From 938d0aa35dbae4cd3c2cb6c10c4e66c055e83c9e Mon Sep 17 00:00:00 2001 From: yuanxinwang Date: Wed, 26 Feb 2020 11:02:27 -0800 Subject: [PATCH 1/2] Add CondStore More test cases for MessageNumberSet null out mailboxinfo, try catch outside for string message number set, stringbuilder for formatter, constant mapper with parsing exception, fix version and developer name Update UT for exception, follow mailbox pattern Fix exception throw java docs for UT Fix javadoc check style for UT Address Fan's comment Undo style fix Update checkstyle_suppressions.xml Update version Remove reduant null and add test cases for null Update FailureType typo Update version Add new test cases for parsing bad string to cover branch. Remove constant length for condstore string Remove constant string length for unchange since Update version --- README.md | 2 +- .../async/data/ExtendedModifiedSinceTerm.java | 127 ++++ .../async/data/ExtensionMailboxInfo.java | 29 +- .../yahoo/imapnio/async/data/FetchResult.java | 33 ++ .../imapnio/async/data/MessageNumberSet.java | 47 ++ .../imapnio/async/data/SearchResult.java | 21 +- .../yahoo/imapnio/async/data/StoreResult.java | 68 +++ .../async/request/AbstractFetchCommand.java | 166 +++++- .../async/request/AbstractSearchCommand.java | 52 +- .../request/AbstractStoreFlagsCommand.java | 108 +++- .../async/request/EntryTypeRequest.java | 35 ++ .../async/request/ExamineFolderCommand.java | 10 + .../async/request/ExtendedSearchSequence.java | 75 +++ .../imapnio/async/request/FetchCommand.java | 22 + .../async/request/ImapArgumentFormatter.java | 47 ++ .../async/request/ImapClientConstants.java | 6 + .../request/OpenFolderActionCommand.java | 34 +- .../async/request/SelectFolderCommand.java | 10 + .../async/request/StoreFlagsCommand.java | 44 ++ .../async/request/UidFetchCommand.java | 50 ++ .../async/request/UidStoreFlagsCommand.java | 43 ++ .../async/response/ImapResponseMapper.java | 140 ++++- .../data/ExtendedModifiedSinceTermTest.java | 72 +++ .../async/data/ExtensionMailboxInfoTest.java | 39 ++ .../imapnio/async/data/FetchResultTest.java | 33 ++ .../async/data/MessageNumberSetTest.java | 217 +++++++ .../imapnio/async/data/SearchResultTest.java | 26 +- .../imapnio/async/data/StoreResultTest.java | 41 ++ .../request/ExamineFolderCommandTest.java | 12 + .../async/request/FetchCommandTest.java | 48 ++ .../request/ImapArgumentFormatterTest.java | 32 + .../async/request/SearchCommandTest.java | 134 +++++ .../request/SelectFolderCommandTest.java | 12 + .../async/request/StoreFlagsCommandTest.java | 79 +++ .../async/request/UidFetchCommandTest.java | 96 +++ .../request/UidStoreFlagsCommandTest.java | 79 +++ .../response/ImapResponseMapperTest.java | 556 +++++++++++++++++- .../yahoo/imapnio/command/ArgumentTest.java | 67 ++- pom.xml | 10 +- 39 files changed, 2626 insertions(+), 96 deletions(-) create mode 100644 core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java create mode 100644 core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java create mode 100644 core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java create mode 100644 core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java create mode 100644 core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java create mode 100644 core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java create mode 100644 core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java create mode 100644 core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java diff --git a/README.md b/README.md index b615d211..5a05a922 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Please refer to the [contributing.md](Contributing.md) for information about how ## Maintainers -Luis Alves: lafa@verizonmedia.com +Yahoo Inc.: https://github.com/yahoo ## License diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java b/core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java new file mode 100644 index 00000000..fad9f47f --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTerm.java @@ -0,0 +1,127 @@ +package com.yahoo.imapnio.async.data; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.mail.Flags; +import javax.mail.Message; +import javax.mail.search.SearchTerm; + +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.request.EntryTypeRequest; + +/** + * This class models search-modsequence with {@code EntryTypeReq} from RFC7162. ABNF from RFC7162 is documented below. + * + *
+ * {@code
+ * 3.1.5.  MODSEQ Search Criterion in SEARCH
+ *
+ *    The MODSEQ criterion for the SEARCH (or UID SEARCH) command allows a
+ *    client to search for the metadata items that were modified since a
+ *    specified moment.
+ *    Syntax: MODSEQ [ ] 
+ * ============================================================================
+ * ABNF:
+ * search-modsequence  = "MODSEQ" [search-modseq-ext] SP mod-sequence-valzer
+ *
+ * entry-name          = entry-flag-name
+ *
+ * entry-flag-name     = DQUOTE "/flags/" attr-flag DQUOTE
+ *                       ;; Each system or user-defined flag 
+ *                       ;; is mapped to "/flags/".
+ *                       ;;
+ *                       ;;  follows the escape rules
+ *                       ;; used by "quoted" string as described in
+ *                       ;; Section 4.3 of [RFC3501]; e.g., for the
+ *                       ;; flag \Seen, the corresponding 
+ *                       ;; is "/flags/\\seen", and for the flag
+ *                       ;; $MDNSent, the corresponding 
+ *                       ;; is "/flags/$mdnsent".
+ *
+ * entry-type-resp     = "priv" / "shared"
+ *                       ;; Metadata item type.
+ *
+ * entry-type-req      = entry-type-resp / "all"
+ *                       ;; Perform SEARCH operation on a private
+ *                       ;; metadata item, shared metadata item,
+ *                       ;; or both.
+ *
+ * }
+ * 
+ */ +public final class ExtendedModifiedSinceTerm extends SearchTerm { + + /** The name of the metadata item. */ + private final Flags entryName; + + /** The type of metadata item. */ + private final EntryTypeRequest entryType; + + /** The given modification sequence. */ + private final long modSeq; + + /** + * Initializes the {@code ExtendedModifiedSinceTerm} class with modification sequence. + * + * @param modSeq modification sequence number + */ + public ExtendedModifiedSinceTerm(final long modSeq) { + this.entryName = null; + this.entryType = null; + this.modSeq = modSeq; + } + + /** + * Initializes the {@code ExtendedModifiedSinceTerm} class with entry name, entry type, and modification sequence. + * + * @param entryName name of the metadata item + * @param entryType type of the metadata item + * @param modSeq modification sequence number + * @throws ImapAsyncClientException if entryName has more than one flag. + */ + public ExtendedModifiedSinceTerm(@Nonnull final Flags entryName, @Nonnull final EntryTypeRequest entryType, final long modSeq) + throws ImapAsyncClientException { + final Flags.Flag[] sf = entryName.getSystemFlags(); // get the system flags + final String[] uf = entryName.getUserFlags(); // get the user flag strings + if (sf.length + uf.length != 1) { + throw new ImapAsyncClientException(ImapAsyncClientException.FailureType.INVALID_INPUT); + } + this.entryName = entryName; + this.entryType = entryType; + this.modSeq = modSeq; + } + + /** + * @return the entry name + */ + @Nullable + public Flags getEntryName() { + return entryName; + } + + /** + * @return the entry type + */ + @Nullable + public EntryTypeRequest getEntryType() { + return entryType; + } + + /** + * @return the modification sequence + */ + public long getModSeq() { + return modSeq; + } + + /** + * This method should never be called for ExtendedModifiedSinceTerm. + * + * @param msg the date comparator is applied to this Message's MODSEQ + * @return UnsupportedOperationException if the method is called + */ + @Override + public boolean match(final Message msg) { + throw new UnsupportedOperationException("This method should not be used. Only for override purpose."); + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java b/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java index 741fec41..1e1760c4 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java +++ b/core/src/main/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfo.java @@ -15,9 +15,15 @@ public class ExtensionMailboxInfo extends MailboxInfo { /** Literal for MAILBOXID. */ private static final String MAILBOX_ID = "MAILBOXID"; + /** Literal for NOMODSEQ. */ + private static final String NOMODSEQ = "NOMODSEQ"; + /** Variable to store mailbox Id. */ private String mailboxId; + /** Variable to indicate whether a server doesn't support the persistent storage of mod-sequencese after enabling CONDSTORE command. */ + private boolean isNoModSeq; + /** * Initializes an instance of {@link ExtensionMailboxInfo} from the server responses for the select or examine command. * @@ -43,16 +49,26 @@ public ExtensionMailboxInfo(@Nonnull final IMAPResponse[] resps) throws ParsingE ir.reset(); continue; } + boolean handled = true; key = key.toUpperCase(); if (key.equals(MAILBOX_ID)) { // example when 26 is the mailbox id:"* OK [MAILBOXID (26)] Ok" final String[] values = ir.readSimpleList(); // reading the string, aka as above example, "(26)", within parentheses if (values != null && values.length >= 1) { mailboxId = values[0]; - resps[i] = null; // Nulls out this element in array to be consistent with MailboxInfo behavior - break; + } else { + handled = false; } + } else if (key.equals(NOMODSEQ)) { // example: * OK [NOMODSEQ] Sorry, this mailbox format doesn't support modsequences + isNoModSeq = true; + } else { + handled = false; + } + + if (handled) { + resps[i] = null; // Nulls out this element in array to be consistent with MailboxInfo behavior + } else { + ir.reset(); // default back the parsing index } - ir.reset(); // default back the parsing index } } @@ -63,4 +79,11 @@ public ExtensionMailboxInfo(@Nonnull final IMAPResponse[] resps) throws ParsingE public String getMailboxId() { return mailboxId; } + + /** + * @return isNoModSeq, true if the server return NOMODSEQ response. + */ + public boolean isNoModSeq() { + return isNoModSeq; + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java b/core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java new file mode 100644 index 00000000..8e785d25 --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/data/FetchResult.java @@ -0,0 +1,33 @@ +package com.yahoo.imapnio.async.data; + +import java.util.List; + +import javax.annotation.Nonnull; + +import com.sun.mail.imap.protocol.FetchResponse; + +/** + * This class provides the list of Fetch responses from fetch command response with parsing from FetchResponseMapper. + */ +public class FetchResult { + + /** The collection of Fetch responses. */ + private final List fetchResponses; + + /** + * Initializes a {@link FetchResult} object with Fetch responses collection. + * + * @param fetchResponses collection of Fetch responses from fetch command result + */ + public FetchResult(@Nonnull final List fetchResponses) { + this.fetchResponses = fetchResponses; + } + + /** + * @return Fetch responses collection from fetch or UID fetch command result + */ + @Nonnull + public List getFetchResponses() { + return fetchResponses; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java b/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java index e61862e3..d1fe15d1 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java +++ b/core/src/main/java/com/yahoo/imapnio/async/data/MessageNumberSet.java @@ -257,4 +257,51 @@ public static String buildString(@Nullable final MessageNumberSet[] msgsets) { } return s.toString(); } + + /** + * Converts an IMAP RFC3501 sequence-set syntax into an array of MessageNumberSet. + * + * @param msgNumbers the message numbers string + * @return the array of MessageNumberSet + * @throws ImapAsyncClientException will not throw + */ + public static MessageNumberSet[] buildMessageNumberSets(@Nullable final String msgNumbers) throws ImapAsyncClientException { + if (msgNumbers == null || msgNumbers.isEmpty()) { + return null; + } + + final String[] msgSetStrs = msgNumbers.split(","); + final MessageNumberSet[] msgSets = new MessageNumberSet[msgSetStrs.length]; + + int i = 0; // msgset index + try { + for (final String element : msgSetStrs) { + if (element.contains(":")) { + final String[] elements = element.split(":"); + if (elements.length != 2) { + throw new ImapAsyncClientException(ImapAsyncClientException.FailureType.INVALID_INPUT); + } + if (elements[1].equals("*")) { // Ex: 1:* + msgSets[i] = new MessageNumberSet(Long.parseLong(elements[0]), LastMessage.LAST_MESSAGE); + } else if (elements[0].equals("*")) { // Ex: *:1 + msgSets[i] = new MessageNumberSet(Long.parseLong(elements[1]), LastMessage.LAST_MESSAGE); + } else { // Ex: 1:2 + msgSets[i] = new MessageNumberSet(Long.parseLong(elements[0]), Long.parseLong(elements[1])); + } + } else { + if (element.equals("*")) { // Ex: * + msgSets[i] = new MessageNumberSet(LastMessage.LAST_MESSAGE); + } else { // Ex: 1 + final long num = Long.parseLong(element); + msgSets[i] = new MessageNumberSet(num, num); + } + } + i++; + } + } catch (final NumberFormatException ex) { + throw new ImapAsyncClientException(ImapAsyncClientException.FailureType.INVALID_INPUT); + } + + return msgSets; + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java b/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java index 413c6d56..44372f4e 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java +++ b/core/src/main/java/com/yahoo/imapnio/async/data/SearchResult.java @@ -6,27 +6,40 @@ import javax.annotation.Nullable; /** - * This class provides the list of message sequence numbers from search command response. + * This class provides the modification sequence and the list of message sequence numbers from search command response. */ public class SearchResult { /** Search command response sequence number, could be message sequence or UID. */ @Nonnull private final List msgNumbers; + /** Modification sequence, is only shown when CondStore is enabled. */ + private final Long highestModSeq; + /** - * Initializes a {@link SearchResult} object with message number collection. + * Initializes a {@link SearchResult} object with message number collection and modification sequence. * * @param msgNumbers collection of message number from search command result + * @param highestModSeq the highest modification sequence from search command result */ - public SearchResult(@Nonnull final List msgNumbers) { + public SearchResult(@Nonnull final List msgNumbers, @Nullable final Long highestModSeq) { this.msgNumbers = msgNumbers; + this.highestModSeq = highestModSeq; } /** * @return message number collection from search command or UID search command result */ - @Nullable + @Nonnull public List getMessageNumbers() { return this.msgNumbers; } + + /** + * @return the highest modification sequence from search command or UID search command result + */ + @Nullable + public Long getHighestModSeq() { + return this.highestModSeq; + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java b/core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java new file mode 100644 index 00000000..6ab580da --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/data/StoreResult.java @@ -0,0 +1,68 @@ +package com.yahoo.imapnio.async.data; + +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.sun.mail.imap.protocol.FetchResponse; + +/** + * This class provides the highest modification sequence, the list of Fetch responses, and the list of modified messages number + * from store command response based on RFC3501 and RFC7162 as following. + * + *
+ * resp-text-code      =/ "HIGHESTMODSEQ" SP mod-sequence-value /
+ *                        "MODIFIED" SP sequence-set
+ * 
+ */ +public class StoreResult { + + /** The highest modification sequence. */ + private Long highestModSeq; + + /** The collection of Fetch responses. */ + private final List fetchResponses; + + /** The collection of message number as sequence-set, only shown when CondStore is enabled. */ + private final MessageNumberSet[] modifiedMsgSets; + + /** + * Initializes a {@link StoreResult} object with the highest modification sequence, Fetch responses collection, + * and modified message number collection. + * + * @param highestModSeq the highest modification from store command result + * @param fetchResponses collection of Fetch responses from store command result + * @param modifiedMsgSets collection of modified message number from store command result + */ + public StoreResult(@Nullable final Long highestModSeq, @Nonnull final List fetchResponses, + @Nullable final MessageNumberSet[] modifiedMsgSets) { + this.highestModSeq = highestModSeq; + this.fetchResponses = fetchResponses; + this.modifiedMsgSets = modifiedMsgSets; + } + + /** + * @return the highest modification sequence from store or UID store command result + */ + @Nullable + public Long getHighestModSeq() { + return highestModSeq; + } + + /** + * @return Fetch responses collection from store or UID store command result + */ + @Nonnull + public List getFetchResponses() { + return fetchResponses; + } + + /** + * @return modified message number collection from store or UID store command result + */ + @Nullable + public MessageNumberSet[] getModifiedMsgSets() { + return modifiedMsgSets; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java index 2c63e73e..ef4d884a 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractFetchCommand.java @@ -3,6 +3,7 @@ import java.nio.charset.StandardCharsets; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import com.yahoo.imapnio.async.data.MessageNumberSet; @@ -10,18 +11,47 @@ import io.netty.buffer.Unpooled; /** - * This class defines IMAP fetch command request from client. ABNF in RFC3501 is described as following: + * This class defines IMAP fetch command request from client. ABNF in RFC3501 and RFC7162 is described as following: * *
  * {@code
- * fetch           = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" /
- *                   fetch-att / "(" fetch-att *(SP fetch-att) ")")
+ * fetch               = "FETCH" SP sequence-set SP ("ALL" / "FULL" / "FAST" /
+ *                       fetch-att / "(" fetch-att *(SP fetch-att) ")")
  *
- * fetch-att       = "ENVELOPE" / "FLAGS" / "INTERNALDATE" /
- *                   "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] /
- *                   "BODY" ["STRUCTURE"] / "UID" /
- *                   "BODY" section ["<" number "." nz-number ">"] /
- *                   "BODY.PEEK" section ["<" number "." nz-number ">"]
+ * fetch-att           = "ENVELOPE" / "FLAGS" / "INTERNALDATE" /
+ *                       "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] /
+ *                       "BODY" ["STRUCTURE"] / "UID" /
+ *                       "BODY" section ["<" number "." nz-number ">"] /
+ *                       "BODY.PEEK" section ["<" number "." nz-number ">"]
+ *
+ * fetch-modifier      =/ chgsince-fetch-mod
+ *                       ;; Conforms to the generic "fetch-modifier"
+ *                       ;; syntax defined in [RFC4466].
+ *
+ * chgsince-fetch-mod  = "CHANGEDSINCE" SP mod-sequence-value
+ *                       ;; CHANGEDSINCE FETCH modifier conforms to
+ *                       ;; the fetch-modifier syntax.
+ *
+ * fetch-att           =/ fetch-mod-sequence
+ *                       ;; Modifies original IMAP4 fetch-att.
+ *
+ * fetch-mod-sequence  = "MODSEQ"
+ *
+ * fetch-mod-resp      = "MODSEQ" SP "(" permsg-modsequence ")"
+ *
+ * permsg-modsequence  = mod-sequence-value
+ *                       ;; Per-message mod-sequence.
+ *
+ * mod-sequence-value  = 1*DIGIT
+ *                       ;; Positive unsigned 63-bit integer
+ *                       ;; (mod-sequence)
+ *                       ;; (1 <= n <= 9,223,372,036,854,775,807).
+ *
+ * rexpunges-fetch-mod =  "VANISHED"
+ *                       ;; VANISHED UID FETCH modifier conforms
+ *                       ;; to the fetch-modifier syntax
+ *                       ;; defined in [RFC4466].  It is only
+ *                       ;; allowed in the UID FETCH command.
  * }
  * 
*/ @@ -39,6 +69,18 @@ public abstract class AbstractFetchCommand extends ImapRequestAdapter { /** Byte array for UID FETCH. */ private static final byte[] UID_FETCH_SP_B = UID_FETCH_SP.getBytes(StandardCharsets.US_ASCII); + /** Changed Since and space. */ + private static final String CHANGEDSINCE_SP = "CHANGEDSINCE "; + + /** Byte array for Changed Since and Space. */ + private static final byte[] CHANGEDSINCE_SP_B = CHANGEDSINCE_SP.getBytes(StandardCharsets.US_ASCII); + + /** Space and Vanished. */ + private static final String SP_VANISHED = " VANISHED"; + + /** Byte array for Space and Vanished. */ + private static final byte[] SP_VANISHED_B = SP_VANISHED.getBytes(StandardCharsets.US_ASCII); + /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ private static final byte[] CRLF_B = { '\r', '\n' }; @@ -54,54 +96,125 @@ public abstract class AbstractFetchCommand extends ImapRequestAdapter { /** True if prepending UID; false otherwise. */ private boolean isUid; + /** Changed since the given modification sequence. */ + private Long changedSince; + + /** Indicate whether vanished flag is used. */ + private boolean isVanished; + /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array and the data + * items. * * @param isUid whether prepending UID * @param msgsets the set of message set * @param items the data items */ - public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items) { - this(isUid, MessageNumberSet.buildString(msgsets), items); + protected AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items) { + this(isUid, MessageNumberSet.buildString(msgsets), items, null, false); } /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array, and the macro. * * @param isUid whether prepending UID * @param msgsets the set of message set * @param macro the macro */ - public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro) { + protected AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro) { this(isUid, MessageNumberSet.buildString(msgsets), macro); } /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the the message numbers string, and the macro. + * + * @param isUid whether prepending UID + * @param msgNumbers the message numbers string + * @param macro the macro + */ + protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final FetchMacro macro) { + this(isUid, msgNumbers, macro, null, false); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the the message numbers string, and the data items. * * @param isUid whether prepending UID * @param msgNumbers the message numbers string * @param items the data items */ protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final String items) { + this(isUid, msgNumbers, items, null, false); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array, the data items, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. + * + * @param isUid whether prepending UID + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option + */ + public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, + @Nullable final Long changedSince, final boolean isVanished) { + this(isUid, MessageNumberSet.buildString(msgsets), items, changedSince, isVanished); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the {@link MessageNumberSet} array, the macro, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. + * + * @param isUid whether prepending UID + * @param msgsets the set of message set + * @param macro the macro + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option + */ + public AbstractFetchCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, + @Nullable final Long changedSince, final boolean isVanished) { + this(isUid, MessageNumberSet.buildString(msgsets), macro, changedSince, isVanished); + } + + /** + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the message numbers string, the data items, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. + * + * @param isUid whether prepending UID + * @param msgNumbers the message numbers string + * @param items the data items + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option + */ + protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final String items, + @Nullable final Long changedSince, final boolean isVanished) { this.isUid = isUid; this.msgNumbers = msgNumbers; this.dataItems = items; this.macro = null; + this.changedSince = changedSince; + this.isVanished = isVanished; } /** - * Initializes a {@link FetchCommand} with the {@link MessageNumberSet} array. + * Initializes a {@link AbstractFetchCommand} with the flag indicate whether prepending UID, the message numbers string, the macro, + * changed since the given modification sequence, and the flag to check whether uid fetch with vanished option. * * @param isUid whether prepending UID * @param msgNumbers the message numbers string * @param macro the macro + * @param changedSince changed since the given modification sequence + * @param isVanished whether uid fetch with vanished option */ - protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final FetchMacro macro) { + protected AbstractFetchCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final FetchMacro macro, + @Nullable final Long changedSince, final boolean isVanished) { this.isUid = isUid; this.msgNumbers = msgNumbers; this.macro = macro; this.dataItems = null; + this.changedSince = changedSince; + this.isVanished = isVanished; } @Override @@ -109,11 +222,18 @@ public void cleanup() { this.msgNumbers = null; this.dataItems = null; this.macro = null; + this.changedSince = null; } @Override public ByteBuf getCommandLineBytes() { - final ByteBuf sb = Unpooled.buffer(); + // Ex:UID FETCH 300:500 (FLAGS) (CHANGEDSINCE 1234 VANISHED) + final int dataItemsSize = dataItems == null ? macro.name().length() : dataItems.length(); + final String changedSinceStr = changedSince == null ? "" : changedSince.toString(); + final int vanishedSize = isVanished ? SP_VANISHED.length() : 0; + final int len = ImapClientConstants.PAD_LEN + dataItemsSize + msgNumbers.length() + vanishedSize; + final ByteBuf sb = Unpooled.buffer(len); + sb.writeBytes(isUid ? UID_FETCH_SP_B : FETCH_SP_B); sb.writeBytes(msgNumbers.getBytes(StandardCharsets.US_ASCII)); sb.writeByte(ImapClientConstants.SPACE); @@ -125,6 +245,18 @@ public ByteBuf getCommandLineBytes() { } else { sb.writeBytes(macro.name().getBytes(StandardCharsets.US_ASCII)); } + + if (changedSince != null) { + sb.writeByte(ImapClientConstants.SPACE); + sb.writeByte(ImapClientConstants.L_PAREN); + sb.writeBytes(CHANGEDSINCE_SP_B); + sb.writeBytes(changedSinceStr.getBytes(StandardCharsets.US_ASCII)); + if (isVanished) { + sb.writeBytes(SP_VANISHED_B); + } + sb.writeByte(ImapClientConstants.R_PAREN); + } + sb.writeBytes(CRLF_B); return sb; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java index 67e2826e..da3d28f4 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractSearchCommand.java @@ -24,26 +24,38 @@ * This class defines IMAP search command request from client. * *
- * search         = "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
- *                   ; CHARSET argument to MUST be registered with IANA
+ * search              = "SEARCH" [SP "CHARSET" SP astring] 1*(SP search-key)
+ *                       ; CHARSET argument to MUST be registered with IANA
  *
- * search-key     = "ALL" / "ANSWERED" / "BCC" SP astring /
- *                  "BEFORE" SP date / "BODY" SP astring /
- *                  "CC" SP astring / "DELETED" / "FLAGGED" /
- *                  "FROM" SP astring / "KEYWORD" SP flag-keyword /
- *                  "NEW" / "OLD" / "ON" SP date / "RECENT" / "SEEN" /
- *                  "SINCE" SP date / "SUBJECT" SP astring /
- *                  "TEXT" SP astring / "TO" SP astring /
- *                  "UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
- *                  "UNKEYWORD" SP flag-keyword / "UNSEEN" /
- *                    ; Above this line were in [IMAP2]
- *                  "DRAFT" / "HEADER" SP header-fld-name SP astring /
- *                  "LARGER" SP number / "NOT" SP search-key /
- *                  "OR" SP search-key SP search-key /
- *                  "SENTBEFORE" SP date / "SENTON" SP date /
- *                  "SENTSINCE" SP date / "SMALLER" SP number /
- *                  "UID" SP sequence-set / "UNDRAFT" / sequence-set /
- *                  "(" search-key *(SP search-key) ")"
+ * search-key          = "ALL" / "ANSWERED" / "BCC" SP astring /
+ *                       "BEFORE" SP date / "BODY" SP astring /
+ *                       "CC" SP astring / "DELETED" / "FLAGGED" /
+ *                       "FROM" SP astring / "KEYWORD" SP flag-keyword /
+ *                       "NEW" / "OLD" / "ON" SP date / "RECENT" / "SEEN" /
+ *                       "SINCE" SP date / "SUBJECT" SP astring /
+ *                       "TEXT" SP astring / "TO" SP astring /
+ *                       "UNANSWERED" / "UNDELETED" / "UNFLAGGED" /
+ *                       "UNKEYWORD" SP flag-keyword / "UNSEEN" /
+ *                         ; Above this line were in [IMAP2]
+ *                       "DRAFT" / "HEADER" SP header-fld-name SP astring /
+ *                       "LARGER" SP number / "NOT" SP search-key /
+ *                       "OR" SP search-key SP search-key /
+ *                       "SENTBEFORE" SP date / "SENTON" SP date /
+ *                       "SENTSINCE" SP date / "SMALLER" SP number /
+ *                       "UID" SP sequence-set / "UNDRAFT" / sequence-set /
+ *                       "(" search-key *(SP search-key) ")"
+ *
+ * search-key          =/ search-modsequence
+ *                       ;; Modifies original IMAP4 search-key.
+ *                       ;;
+ *                       ;; This change applies to all commands
+ *                       ;; referencing this non-terminal -- in
+ *                       ;; particular, SEARCH, SORT, and THREAD.
+ *
+ * search-modsequence  = "MODSEQ" [search-modseq-ext] SP
+ *                          mod-sequence-valzer
+ *
+ * search-modseq-ext   = SP entry-name SP entry-type-req
  * 
*/ public abstract class AbstractSearchCommand extends ImapRequestAdapter { @@ -124,7 +136,7 @@ protected AbstractSearchCommand(final boolean isUid, @Nullable final String msgN this.charset = SearchSequence.isAscii(term) ? null : StandardCharsets.UTF_8.name(); if (term != null) { - final SearchSequence searchSeq = new SearchSequence(); + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); this.searchExpr = searchSeq.generateSequence(term, charset == null ? null : MimeUtility.javaCharset(charset)); } this.isLiteralPlusEnabled = (capa != null) ? capa.hasCapability(ImapClientConstants.LITERAL_PLUS) : false; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java index 5593bb12..7b335ace 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/AbstractStoreFlagsCommand.java @@ -15,22 +15,34 @@ * *
  *
- * store           = "STORE" SP sequence-set SP store-att-flags
+ * store                = "STORE" SP sequence-set SP store-att-flags
  *
- * store-att-flags = (["+" / "-"] "FLAGS" [".SILENT"]) SP
- *                   (flag-list / (flag *(SP flag)))
- * flag-list       = "(" [flag *(SP flag)] ")"
- * flag            = "\Answered" / "\Flagged" / "\Deleted" /
- *                   "\Seen" / "\Draft" / flag-keyword / flag-extension
- *                   ; Does not include "\Recent"
+ * store-att-flags      = (["+" / "-"] "FLAGS" [".SILENT"]) SP
+ *                        (flag-list / (flag *(SP flag)))
+ * flag-list            = "(" [flag *(SP flag)] ")"
  *
- * flag-extension  = "\" atom
- *                   ; Future expansion.  Client implementations
- *                   ; MUST accept flag-extension flags.  Server
- *                   ; implementations MUST NOT generate
- *                   ; flag-extension flags except as defined by
- *                   ; future standard or standards-track
- *                   ; revisions of this specification.
+ * flag                 = "\Answered" / "\Flagged" / "\Deleted" /
+ *                        "\Seen" / "\Draft" / flag-keyword / flag-extension
+ *                        ; Does not include "\Recent"
+ *
+ * flag-extension       = "\" atom
+ *                        ; Future expansion.  Client implementations
+ *                        ; MUST accept flag-extension flags.  Server
+ *                        ; implementations MUST NOT generate
+ *                        ; flag-extension flags except as defined by
+ *                        ; future standard or standards-track
+ *                        ; revisions of this specification.
+ *
+ * store-modifier      =/ "UNCHANGEDSINCE" SP mod-sequence-valzer
+ *                       ;; Only a single "UNCHANGEDSINCE" may be
+ *                       ;; specified in a STORE operation.
+ *
+ * mod-sequence-value  = 1*DIGIT
+ *                       ;; Positive unsigned 63-bit integer
+ *                       ;; (mod-sequence)
+ *                       ;; (1 \leq n \leq 9,223,372,036,854,775,807).
+ *
+ * mod-sequence-valzer = "0" / mod-sequence-value
  * 
*/ public abstract class AbstractStoreFlagsCommand extends ImapRequestAdapter { @@ -62,6 +74,15 @@ public abstract class AbstractStoreFlagsCommand extends ImapRequestAdapter { /** Byte array for SILENT. */ private static final byte[] SILENT_B = SILENT.getBytes(StandardCharsets.US_ASCII); + /** Literal for UNCHANGEDSINCE and Space. */ + private static final String UNCHANGEDSINCE_SP = "UNCHANGEDSINCE "; + + /** Byte array for UNCHANGEDSINCE and Space. */ + private static final byte[] UNCHANGEDSINCE_SP_B = UNCHANGEDSINCE_SP.getBytes(StandardCharsets.US_ASCII); + + /** Unchanged since the modification sequence. */ + private Long unchangedSince; + /** Flag whether adding UID before store command. */ private boolean isUid; @@ -93,7 +114,24 @@ protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final MessageN } /** - * Initializes a {@link AbstractStoreFlagsCommand} with string form message numbers (could be sequence sets or UIDs) and all other parameters. + * Initializes a {@link AbstractStoreFlagsCommand} with the MessageNumberSet array, flags, action, silent flag whether server should return new + * values, and unchanged since the given modification sequence. + * + * @param isUid whether to have UID prepended + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently + * @param unchangedSince unchanged since the given modification sequence + */ + protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, + @Nonnull final FlagsAction action, final boolean silent, @Nonnull final Long unchangedSince) { + this(isUid, MessageNumberSet.buildString(msgsets), flags, action, silent, unchangedSince); + } + + /** + * Initializes a {@link AbstractStoreFlagsCommand} with string form message numbers (could be sequence sets or UIDs), flags, action, + * and silent flag whether server should return new value. * * @param isUid whether to have UID prepended * @param msgNumbers the message id @@ -108,6 +146,28 @@ protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final String m this.flags = flags; this.action = action; this.isSilent = silent; + this.unchangedSince = null; + } + + /** + * Initializes a {@link AbstractStoreFlagsCommand} with string form message numbers (could be sequence sets or UIDs), flags, action, + * silent flag whether server should return new values, and unchanged since the given modification sequence. + * + * @param isUid whether to have UID prepended + * @param msgNumbers the message id + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently + * @param unchangedSince unchanged since the given modification sequence + */ + protected AbstractStoreFlagsCommand(final boolean isUid, @Nonnull final String msgNumbers, @Nonnull final Flags flags, + @Nonnull final FlagsAction action, final boolean silent, @Nonnull final Long unchangedSince) { + this.isUid = isUid; + this.msgNumbers = msgNumbers; + this.flags = flags; + this.action = action; + this.isSilent = silent; + this.unchangedSince = unchangedSince; } @Override @@ -115,16 +175,29 @@ public void cleanup() { this.msgNumbers = null; this.flags = null; this.action = null; + this.unchangedSince = null; } @Override public ByteBuf getCommandLineBytes() { // Ex:STORE 2:4 +FLAGS (\Deleted) - final ByteBuf sb = Unpooled.buffer(); + final ImapArgumentFormatter argWriter = new ImapArgumentFormatter(); + final String flagListString = argWriter.buildFlagString(flags); + final String unchangedSinceStr = unchangedSince == null ? "" : unchangedSince.toString(); + final int len = ImapClientConstants.PAD_LEN + msgNumbers.length() + flagListString.length(); + final ByteBuf sb = Unpooled.buffer(len); sb.writeBytes(isUid ? UID_STORE_SP_B : STORE_SP_B); sb.writeBytes(msgNumbers.getBytes(StandardCharsets.US_ASCII)); sb.writeByte(ImapClientConstants.SPACE); + if (unchangedSince != null) { + sb.writeByte(ImapClientConstants.L_PAREN); + sb.writeBytes(UNCHANGEDSINCE_SP_B); + sb.writeBytes(unchangedSinceStr.getBytes(StandardCharsets.US_ASCII)); + sb.writeByte(ImapClientConstants.R_PAREN); + sb.writeByte(ImapClientConstants.SPACE); + } + if (action == FlagsAction.ADD) { sb.writeByte(ImapClientConstants.PLUS); } else if (action == FlagsAction.REMOVE) { @@ -138,9 +211,8 @@ public ByteBuf getCommandLineBytes() { } // buildFlagString generates "(" [flag *(SP flag)] ")" - final ImapArgumentFormatter argWriter = new ImapArgumentFormatter(); sb.writeByte(ImapClientConstants.SPACE); - sb.writeBytes(argWriter.buildFlagString(flags).getBytes(StandardCharsets.US_ASCII)); + sb.writeBytes(flagListString.getBytes(StandardCharsets.US_ASCII)); sb.writeBytes(CRLF_B); return sb; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java b/core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java new file mode 100644 index 00000000..809870f6 --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/request/EntryTypeRequest.java @@ -0,0 +1,35 @@ +package com.yahoo.imapnio.async.request; + +import javax.annotation.Nonnull; + +/** + * Metadata item type (entry-type-req) for search command with the modification sequence extension. + */ +public enum EntryTypeRequest { + /** Entry type for a private metadata item. */ + PRIVATE("PRIV"), + /** Entry type for a shared metadata item. */ + SHARED("SHARED"), + /** Entry type to indicate to use the biggest value among "PRIV" and "SHARED" mod-sequences for the metadata item. */ + ALL("ALL"); + + /** Name of entry type. */ + private final String typeName; + + /** + * Initializes a {@link EntryTypeRequest} with type name. + * + * @param typeName name of entry type + */ + EntryTypeRequest(@Nonnull final String typeName) { + this.typeName = typeName; + } + + /** + * @return the entry type name + */ + @Nonnull + public String getTypeName() { + return this.typeName; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java index 7177ab23..d039bfef 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ExamineFolderCommand.java @@ -33,6 +33,16 @@ public ExamineFolderCommand(@Nonnull final String folderName, @Nonnull final QRe super(EXAMINE, folderName, qResyncParameter); } + /** + * Initializes a {@link ExamineFolderCommand}. + * + * @param folderName folder name to examine + * @param isCondstoreEnabled whether to enable CondStore + */ + public ExamineFolderCommand(@Nonnull final String folderName, final boolean isCondstoreEnabled) { + super(EXAMINE, folderName, isCondstoreEnabled); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.EXAMINE_FOLDER; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java b/core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java new file mode 100644 index 00000000..74d09793 --- /dev/null +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ExtendedSearchSequence.java @@ -0,0 +1,75 @@ +package com.yahoo.imapnio.async.request; + +import java.io.IOException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.mail.search.SearchException; +import javax.mail.search.SearchTerm; + +import com.sun.mail.iap.Argument; +import com.sun.mail.imap.protocol.SearchSequence; +import com.yahoo.imapnio.async.data.ExtendedModifiedSinceTerm; + +/** + * This class extends the search sequence for modification sequence with the optional fields entry + * name and type defined in https://tools.ietf.org/html/rfc7162#section-3.1.5. + */ +public class ExtendedSearchSequence extends SearchSequence { + + /** Literal for MODSEQ. */ + private static final String MODSEQ = "MODSEQ"; + + /** + * Generate the IMAP search sequence for the given search expression. + * + * @param term the search term + * @return the IMAP search sequence argument + * @throws SearchException when search too complex + * @throws IOException when failing to convert the given string into bytes in the specified charset + */ + public Argument generateSequence(@Nonnull final SearchTerm term) throws SearchException, IOException { + if (term instanceof ExtendedModifiedSinceTerm) { + return modifiedSince((ExtendedModifiedSinceTerm) term); + } else { + return super.generateSequence(term, null); + } + } + + /** + * Generate the IMAP search sequence for the given search expression. + * + * @param term the search term + * @param charset the character set + * @return the IMAP search sequence argument + * @throws SearchException when search too complex + * @throws IOException when failing to convert the given string into bytes in the specified charset + */ + @Override + public Argument generateSequence(@Nonnull final SearchTerm term, @Nullable final String charset) throws SearchException, IOException { + if (term instanceof ExtendedModifiedSinceTerm) { + return modifiedSince((ExtendedModifiedSinceTerm) term); + } else { + return super.generateSequence(term, charset); + } + } + + /** + * Modification of SearchSequence modifiedSince method to support optional entry name and entry type. + * + * @param term the extended modified since search term + * @return the IMAP search sequence argument + */ + private Argument modifiedSince(@Nonnull final ExtendedModifiedSinceTerm term) { + final Argument result = new Argument(); + result.writeAtom(MODSEQ); + if (term.getEntryName() != null && term.getEntryType() != null) { + final ImapArgumentFormatter argWriter = new ImapArgumentFormatter(); + result.writeAtom(argWriter.buildEntryFlagName(term.getEntryName())); + result.writeAtom(term.getEntryType().getTypeName()); + } + + result.writeNumber(term.getModSeq()); + return result; + } +} diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java index bd51ea0f..1e1cc882 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/FetchCommand.java @@ -29,6 +29,28 @@ public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Fe super(false, msgsets, macro); } + /** + * Initializes a {@link FetchCommand} with the {@code MessageNumberSet} array, fetch items, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + */ + public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, @Nonnull final Long changedSince) { + super(false, msgsets, items, changedSince, false); + } + + /** + * Initializes a {@link FetchCommand} with the {@code MessageNumberSet} array, macro, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param macro the macro + * @param changedSince changed since the given modification sequence + */ + public FetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, @Nonnull final Long changedSince) { + super(false, msgsets, macro, changedSince, false); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.FETCH; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java b/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java index bfb02973..bba521aa 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ImapArgumentFormatter.java @@ -226,4 +226,51 @@ String buildFlagString(@Nonnull final Flags flags) { sb.append(ImapClientConstants.R_PAREN); // terminate flag_list return sb.toString(); } + + /** + * Creates an IMAP entry-flag-name from the given Flags with only one flag object. The following is the ABNF from RFC7162 + *
+     * entry-flag-name     = DQUOTE "/flags/" attr-flag DQUOTE
+     *                        ;; Each system or user-defined flag 
+     *                        ;; is mapped to "/flags/".
+     *                        ;;
+     *                        ;;  follows the escape rules
+     *                        ;; used by "quoted" string as described in
+     *                        ;; Section 4.3 of [RFC3501]; e.g., for the
+     *                        ;; flag \Seen, the corresponding 
+     *                        ;; is "/flags/\\seen", and for the flag
+     *                        ;; $MDNSent, the corresponding 
+     *                        ;; is "/flags/$mdnsent".
+     * 
+ * + * @param flags the flags with one flag + * @return the entry flag name string + */ + String buildEntryFlagName(@Nonnull final Flags flags) { + final Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags + final String[] uf = flags.getUserFlags(); // get the user flag strings + final StringBuilder s = new StringBuilder("\"/flags/"); // start of entry flag name + + if (sf.length == 1) { + s.append(ImapClientConstants.BACKSLASH); + if (sf[0] == Flags.Flag.ANSWERED) { + s.append(ANSWERED); + } else if (sf[0] == Flags.Flag.DELETED) { + s.append(DELETED); + } else if (sf[0] == Flags.Flag.DRAFT) { + s.append(DRAFT); + } else if (sf[0] == Flags.Flag.FLAGGED) { + s.append(FLAGGED); + } else if (sf[0] == Flags.Flag.RECENT) { + s.append(RECENT); + } else if (sf[0] == Flags.Flag.SEEN) { + s.append(SEEN); + } + } else { // user flag == 1 + s.append(uf[0]); + } + + s.append(ImapClientConstants.DQUOTA); + return s.toString(); + } } diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java b/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java index a58f3af2..044321ba 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/ImapClientConstants.java @@ -47,6 +47,12 @@ final class ImapClientConstants { /** Extra buffer length for command line builder to add. */ static final int PAD_LEN = 100; + /** Literal for double quota. */ + static final char DQUOTA = '\"'; + + /** Literal for back slash. */ + static final char BACKSLASH = '\\'; + /** * Private constructor to avoid constructing instance of this class. */ diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java index 3062cf9c..9e81797c 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/OpenFolderActionCommand.java @@ -20,6 +20,12 @@ abstract class OpenFolderActionCommand extends ImapRequestAdapter { /** Byte array for CR and LF, keeping the array local so it cannot be modified by others. */ private static final byte[] CRLF_B = { '\r', '\n' }; + /** Literal for CONDSTORE. */ + private static final String SP_CONDSTORE = " (CONDSTORE)"; + + /** Byte array for CONDSTORE. */ + private static final byte[] SP_CONDSTORE_B = SP_CONDSTORE.getBytes(StandardCharsets.US_ASCII); + /** Command operator, for example, "SELECT". */ private String op; @@ -29,6 +35,9 @@ abstract class OpenFolderActionCommand extends ImapRequestAdapter { /** Optional QResync parameter. */ private QResyncParameter qResyncParameter; + /** Optional CondStore parameter. */ + private boolean isCondStoreEnabled; + /** * Initializes a {@link OpenFolderActionCommand}. * @@ -39,6 +48,7 @@ protected OpenFolderActionCommand(@Nonnull final String op, @Nonnull final Strin this.op = op; this.folderName = folderName; this.qResyncParameter = null; + this.isCondStoreEnabled = false; } /** @@ -52,6 +62,21 @@ public OpenFolderActionCommand(@Nonnull final String op, @Nonnull final String f this.op = op; this.folderName = folderName; this.qResyncParameter = qResyncParameter; + this.isCondStoreEnabled = false; + } + + /** + * Initializes a {@link OpenFolderActionCommand}. + * + * @param op command operator + * @param folderName folder name + * @param isCondStoreEnabled whether to enable CondStore + */ + protected OpenFolderActionCommand(@Nonnull final String op, @Nonnull final String folderName, final boolean isCondStoreEnabled) { + this.op = op; + this.folderName = folderName; + this.qResyncParameter = null; + this.isCondStoreEnabled = isCondStoreEnabled; } @Override @@ -83,8 +108,11 @@ public ByteBuf getCommandLineBytes() throws ImapAsyncClientException { sb.append("))"); qResyncParameterSize = sb.length(); } + + final int condStoreSize = isCondStoreEnabled ? SP_CONDSTORE.length() : 0; + // 2 * base64Folder.length(): assuming every char needs to be escaped, goal is eliminating resizing, and avoid complex length calculation - final int len = 2 * base64Folder.length() + ImapClientConstants.PAD_LEN + qResyncParameterSize; + final int len = 2 * base64Folder.length() + ImapClientConstants.PAD_LEN + qResyncParameterSize + condStoreSize; final ByteBuf byteBuf = Unpooled.buffer(len); byteBuf.writeBytes(op.getBytes(StandardCharsets.US_ASCII)); byteBuf.writeByte(ImapClientConstants.SPACE); @@ -97,6 +125,10 @@ public ByteBuf getCommandLineBytes() throws ImapAsyncClientException { byteBuf.writeBytes(sb.toString().getBytes(StandardCharsets.US_ASCII)); } + if (isCondStoreEnabled) { + byteBuf.writeBytes(SP_CONDSTORE_B); + } + byteBuf.writeBytes(CRLF_B); return byteBuf; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java index bd567082..42ae26fd 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/SelectFolderCommand.java @@ -31,6 +31,16 @@ public SelectFolderCommand(@Nonnull final String folderName, @Nonnull final QRes super(SELECT, folderName, qResyncParameter); } + /** + * Initializes a {@link SelectFolderCommand}. + * + * @param folderName folder name to select + * @param isCondstoreEnabled whether to enable CondStore + */ + public SelectFolderCommand(@Nonnull final String folderName, final boolean isCondstoreEnabled) { + super(SELECT, folderName, isCondstoreEnabled); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.SELECT_FOLDER; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java index 04baf568..cf65565e 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/StoreFlagsCommand.java @@ -47,6 +47,50 @@ public StoreFlagsCommand(@Nonnull final String msgNumbers, @Nonnull final Flags super(false, msgNumbers, flags, action, silent); } + /** + * Initializes a {@link StoreFlagsCommand} with the MessageNumberSet array, Flags, action, and unchanged since modification sequence. + * Requests server to return the new value. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param unchangedSince unchanged since the given modification sequence + */ + public StoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + @Nonnull final Long unchangedSince) { + super(false, msgsets, flags, action, false, unchangedSince); + } + + /** + * Initializes a {@link StoreFlagsCommand} with the MessageNumberSet array, flags, action, flag whether to request server to return the new + * value, and unchanged since modification sequence. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently + * @param unchangedSince unchanged since the given modification sequence + */ + public StoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(false, msgsets, flags, action, silent, unchangedSince); + } + + /** + * Initializes a {@link StoreFlagsCommand} with string form message numbers, Flags, action, flag whether to request server to return the new + * value. + * + * @param msgNumbers the message numbers in string format + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently; false if requesting server to return the new values + * @param unchangedSince unchanged since the given modification sequence + */ + public StoreFlagsCommand(@Nonnull final String msgNumbers, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(false, msgNumbers, flags, action, silent, unchangedSince); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.STORE_FLAGS; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java index 712c3397..a8a41172 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/UidFetchCommand.java @@ -49,6 +49,56 @@ public UidFetchCommand(@Nonnull final String uids, @Nonnull final FetchMacro mac super(true, uids, macro); } + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, data items, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, @Nonnull final Long changedSince) { + super(true, msgsets, items, changedSince, false); + } + + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, macro, and changed since the modification sequence. + * + * @param msgsets the set of message set + * @param macro the macro, for example, ALL + * @param changedSince changed since the given modification sequence + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, @Nonnull final Long changedSince) { + super(true, msgsets, macro, changedSince, false); + } + + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, data items, changed since the modification sequence, + * and isVanished flag. + * + * @param msgsets the set of message set + * @param items the data items + * @param changedSince changed since the given modification sequence + * @param isVanished the flag to check whether uid fetch with isVanished option + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final String items, @Nonnull final Long changedSince, + final boolean isVanished) { + super(true, msgsets, items, changedSince, isVanished); + } + + /** + * Initializes a {@link UidFetchCommand} with the {@code MessageNumberSet} array, macro, changed since the modification sequence, + * and isVanished flag. + * + * @param msgsets the set of message set + * @param macro the macro, for example, ALL + * @param changedSince changed since the given modification sequence + * @param isVanished the flag to check whether uid fetch with isVanished option + */ + public UidFetchCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final FetchMacro macro, @Nonnull final Long changedSince, + final boolean isVanished) { + super(true, msgsets, macro, changedSince, isVanished); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.UID_FETCH; diff --git a/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java b/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java index d8239872..95c94660 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java +++ b/core/src/main/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommand.java @@ -48,6 +48,49 @@ public UidStoreFlagsCommand(@Nonnull final String uids, @Nonnull final Flags fla super(true, uids, flags, action, silent); } + /** + * Initializes a {@link UidStoreFlagsCommand} with the MessageNumberSet array, Flags and action. Requests server to return the new value. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param unchangedSince unchanged since the given modification sequence + */ + public UidStoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + @Nonnull final Long unchangedSince) { + super(true, msgsets, flags, action, false, unchangedSince); + } + + /** + * Initializes a {@link UidStoreFlagsCommand} with the MessageNumberSet array, Flags, action, flag whether to request server to return the new + * value, and unchanged since the modification sequence. + * + * @param msgsets the set of message set + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently; false if requesting server to return the new values + * @param unchangedSince unchanged since the given modification sequence + */ + public UidStoreFlagsCommand(@Nonnull final MessageNumberSet[] msgsets, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(true, msgsets, flags, action, silent, unchangedSince); + } + + /** + * Initializes a {@link UidStoreFlagsCommand} with string form message numbers, Flags, action, flag whether to request server to return the new + * value, and the unchanged since the given modification sequence. + * + * @param uids the string representing UID based on RFC3501 + * @param flags the flags to be stored + * @param action whether to replace, add or remove the flags + * @param silent true if asking server to respond silently; false if requesting server to return the new values + * @param unchangedSince unchanged since the given modification sequence + */ + public UidStoreFlagsCommand(@Nonnull final String uids, @Nonnull final Flags flags, @Nonnull final FlagsAction action, + final boolean silent, @Nonnull final Long unchangedSince) { + super(true, uids, flags, action, silent, unchangedSince); + } + @Override public ImapRFCSupportedCommandType getCommandType() { return ImapRFCSupportedCommandType.UID_STORE_FLAGS; diff --git a/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java b/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java index 06acf2c5..8476464b 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java +++ b/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java @@ -1,5 +1,6 @@ package com.yahoo.imapnio.async.response; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -14,9 +15,12 @@ import javax.mail.Folder; import com.sun.mail.iap.ParsingException; +import com.sun.mail.iap.ProtocolException; import com.sun.mail.iap.Response; import com.sun.mail.imap.AppendUID; import com.sun.mail.imap.CopyUID; +import com.sun.mail.imap.protocol.FetchItem; +import com.sun.mail.imap.protocol.FetchResponse; import com.sun.mail.imap.protocol.IMAPResponse; import com.sun.mail.imap.protocol.ListInfo; import com.sun.mail.imap.protocol.MailboxInfo; @@ -26,10 +30,13 @@ import com.yahoo.imapnio.async.data.EnableResult; import com.yahoo.imapnio.async.data.ExtensionListInfo; import com.yahoo.imapnio.async.data.ExtensionMailboxInfo; +import com.yahoo.imapnio.async.data.FetchResult; import com.yahoo.imapnio.async.data.IdResult; import com.yahoo.imapnio.async.data.ListInfoList; import com.yahoo.imapnio.async.data.ListStatusResult; +import com.yahoo.imapnio.async.data.MessageNumberSet; import com.yahoo.imapnio.async.data.SearchResult; +import com.yahoo.imapnio.async.data.StoreResult; import com.yahoo.imapnio.async.exception.ImapAsyncClientException; import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; @@ -41,6 +48,18 @@ public class ImapResponseMapper { /** APPENDUID keyword. */ private static final String APPENDUID = "APPENDUID"; + /** HIGHESTMODSEQ keyword. */ + private static final String HIGHESTMODSEQ = "HIGHESTMODSEQ"; + + /** MODIFIED keyword. */ + private static final String MODIFIED = "MODIFIED"; + + /** MODSEQ keyword. */ + private static final String MODSEQ = "MODSEQ"; + + /** FETCH keyword. */ + private static final String FETCH = "FETCH"; + /** EQUAL sign. */ private static final String EQUAL = "="; @@ -50,6 +69,9 @@ public class ImapResponseMapper { /** ] char. */ private static final char R_BRACKET = ']'; + /** ( char. */ + private static final char L_PAREN = '('; + /** Inner class instance parser. */ private ImapResponseParser parser; @@ -67,8 +89,8 @@ public ImapResponseMapper() { * @param content list of IMAPResponse obtained from server. * @param valueType class name that this api will convert to. * @return the serialized object - * @throws ParsingException if underlying input contains invalid content of type for the returned type * @throws ImapAsyncClientException when target class to covert to is not supported + * @throws ParsingException if underlying input contains invalid content of type for the returned type */ @SuppressWarnings("unchecked") @Nonnull @@ -107,6 +129,33 @@ public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Cla if (valueType == EnableResult.class) { return (T) parser.parseToEnableResult(content); } + if (valueType == FetchResult.class) { + return (T) parser.parseToFetchResult(content, new FetchItem[0]); + } + if (valueType == StoreResult.class) { + return (T) parser.parseToStoreResult(content); + } + + throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); + } + + /** + * Method to deserialize IMAPResponse content from given IMAPResponse content String. + * + * @param the object to serialize to + * @param content list of IMAPResponse obtained from server. + * @param valueType class name that this api will convert to. + * @param extensionItems the array of extension FetchItem + * @return the serialized object + * @throws ImapAsyncClientException when target class to covert to is not supported + */ + @Nonnull + public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType, @Nonnull final FetchItem[] extensionItems) + throws ImapAsyncClientException { + if (valueType == FetchResult.class) { + return (T) parser.parseToFetchResult(content, extensionItems); + } + throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); } @@ -524,6 +573,8 @@ private SearchResult parseToSearchResult(@Nonnull final IMAPResponse[] ir) throw } final List v = new ArrayList(); // will always return a non-null array + Long modSeq = null; + // Grab all SEARCH responses long num; for (final IMAPResponse sr : ir) { @@ -532,10 +583,95 @@ private SearchResult parseToSearchResult(@Nonnull final IMAPResponse[] ir) throw while ((num = sr.readLong()) != -1) { v.add(Long.valueOf(num)); } + if (sr.readByte() == L_PAREN) { + final String s = sr.readAtom(); + if (MODSEQ.equals(s)) { + modSeq = sr.readLong(); + } + } + } + } + + return new SearchResult(v, modSeq); + } + + /** + * Parses the responses from Store command and UID Store command to a {@link StoreResult} object. + * + * @param ir the list of responses from Store or UID Store command, the input responses array should contain the tagged/final one + * @return StoreResult object constructed based on the given IMAPResponse array, + * @throws ImapAsyncClientException when tagged response is bad or given response length is 0 or fail to parse Fetch Response + */ + @Nonnull + private StoreResult parseToStoreResult(@Nonnull final IMAPResponse[] ir) + throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + taggedResponse.skipSpaces(); + MessageNumberSet[] modifiedMsgsets = null; // only one response will contain modified numbers + if (!taggedResponse.isBAD() && taggedResponse.readByte() == (byte) L_BRACKET && MODIFIED.equals(taggedResponse.readAtom())) { + // ex: d105 OK [MODIFIED 7,9] Conditional STORE failed + modifiedMsgsets = MessageNumberSet.buildMessageNumberSets(taggedResponse.readAtom()); + } else if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + final List fetchResponses = new ArrayList<>(); // will always return a non-null array + + Long highestModSeq = null; + + for (final IMAPResponse sr: ir) { + sr.skipSpaces(); + if (sr.keyEquals(FETCH)) { + try { + fetchResponses.add(new FetchResponse(sr)); + } catch (final IOException | ProtocolException e) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + } else if (sr.readByte() == (byte) L_BRACKET) { // HIGHESTMODSEQ or MODIFIED + final String responseCode = sr.readAtom(); + if (HIGHESTMODSEQ.equals(responseCode)) { + highestModSeq = sr.readLong(); + } + } + } + + return new StoreResult(highestModSeq, fetchResponses, modifiedMsgsets); + } + + /** + * Parses the responses from Fetch command and UID Fetch command to a {@link FetchResult} object. + * + * @param ir the list of responses from Fetch or UID Fetch command, the input responses array should contain the tagged/final one + * @param extensionItems the array of extension FetchItem + * @return FetchResult object constructed based on the given IMAPResponse array, + * @throws ImapAsyncClientException when tagged response is not OK or given response length is 0 or fail to parse Fetch Response + */ + @Nonnull + private FetchResult parseToFetchResult(@Nonnull final IMAPResponse[] ir, @Nonnull final FetchItem[] extensionItems) + throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final List fetchResponses = new ArrayList<>(); // will always return a non-null array + + for (final IMAPResponse sr: ir) { + if (sr.keyEquals(FETCH)) { + try { + fetchResponses.add(new FetchResponse(sr, extensionItems)); + } catch (final IOException | ProtocolException e) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } } } - return new SearchResult(v); + return new FetchResult(fetchResponses); } } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java new file mode 100644 index 00000000..9febde66 --- /dev/null +++ b/core/src/test/java/com/yahoo/imapnio/async/data/ExtendedModifiedSinceTermTest.java @@ -0,0 +1,72 @@ +package com.yahoo.imapnio.async.data; + +import javax.mail.Flags; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.request.EntryTypeRequest; + +/** + * Unit test for {@code ExtendedModifiedSinceTerm}. + */ +public class ExtendedModifiedSinceTermTest { + + /** + * Tests ExtendedModifiedSinceTerm constructor and getters. + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testExtendedModifiedSinceTermWithOptionalField() throws ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.ALL, 1L); + + Assert.assertEquals(extendedModifiedSinceTerm.getModSeq(), 1L, "getModSeq() mismatched."); + Assert.assertNotNull(extendedModifiedSinceTerm.getEntryName(), "getEntryName() should not return null."); + Assert.assertTrue(extendedModifiedSinceTerm.getEntryName().contains(Flags.Flag.SEEN), "getEntryName() mismatched."); + Assert.assertNotNull(extendedModifiedSinceTerm.getEntryType(), "getEntryType() should not return null."); + Assert.assertEquals(extendedModifiedSinceTerm.getEntryType().name(), "ALL", "getEntryType() mismatched."); + } + + /** + * Tests ExtendedModifiedSinceTerm constructor and getters. + */ + @Test + public void testExtendedModifiedSinceTermWithoutOptionalField() { + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + + Assert.assertEquals(extendedModifiedSinceTerm.getModSeq(), 1L, "getModSeq() mismatched."); + Assert.assertNull(extendedModifiedSinceTerm.getEntryName(), "Entry name should be null"); + Assert.assertNull(extendedModifiedSinceTerm.getEntryType(), "Entry type should be null."); + } + + /** + * Tests ExtendedModifiedSinceTerm match throws UnsupportedOperationException. + */ + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testExtendedModifiedSinceTermMatchException() { + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + extendedModifiedSinceTerm.match(null); + } + + /** + * Tests buildEntryFlagName with ImapAsyncClientException. + */ + @Test + public void testBuildEntryFlagNameFailed() { + final Flags flags = new Flags(); + flags.add(Flags.Flag.ANSWERED); + flags.add(Flags.Flag.DELETED); + ImapAsyncClientException actual = null; + + try { + new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.ALL, 1L); + } catch (final ImapAsyncClientException e) { + actual = e; + } + Assert.assertNotNull(actual, "Should throw ImapAsyncClientException"); + Assert.assertEquals(actual.getFailureType(), ImapAsyncClientException.FailureType.INVALID_INPUT, "FailureType should be INVALID_INPUT"); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java index 3fe13787..39a07b58 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/data/ExtensionMailboxInfoTest.java @@ -48,6 +48,7 @@ public void testCreateExtensionMailboxInfoSuccess() throws IOException, Protocol Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); Assert.assertEquals(minfo.getMailboxId(), "214-mailbox", "MailboxId mismatched."); + Assert.assertFalse(minfo.isNoModSeq(), "isNoModSeq() mismatched."); Assert.assertNull(content[7], "This element should be nulled out"); } @@ -198,4 +199,42 @@ public void testCreateExtensionMailboxInfoNoValueInParenthsis() throws IOExcepti Assert.assertNotNull(content[7], "This element should not be nulled out"); Assert.assertEquals(content[7].getRest(), "[MAILBOXID ()] Ok", "The index is not reset to the point of the status code."); } + + /** + * Tests calling constructor with no mod seq successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testCreateExtensionMailboxInfoSuccessNoModSequence() throws IOException, ProtocolException { + + final IMAPResponse[] content = new IMAPResponse[9]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [MAILBOXID (214-mailbox)] Ok"); + content[7] = new IMAPResponse("* OK [NOMODSEQ] Sorry, this mailbox format doesn't support modsequences"); + content[8] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); + final ExtensionMailboxInfo minfo = new ExtensionMailboxInfo(content); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, -1, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + Assert.assertEquals(minfo.getMailboxId(), "214-mailbox", "MailboxId mismatched."); + Assert.assertTrue(minfo.isNoModSeq(), "isNoModSeq() mismatched."); + Assert.assertNull(content[6], "This element should be nulled out"); + Assert.assertNull(content[7], "This element should be nulled out"); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java new file mode 100644 index 00000000..31694196 --- /dev/null +++ b/core/src/test/java/com/yahoo/imapnio/async/data/FetchResultTest.java @@ -0,0 +1,33 @@ +package com.yahoo.imapnio.async.data; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.IMAPResponse; + +/** + * Unit test for {@code FetchResult}. + */ +public class FetchResultTest { + /** + * Tests FetchResult constructor and getters. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testFetchResult() throws IOException, ProtocolException { + final FetchResponse fetchResponse = new FetchResponse(new IMAPResponse("* 1 FETCH (UID 4 MODSEQ (12121231000))")); + final List expectedFetchResponses = Collections.singletonList(fetchResponse); + final FetchResult fr = new FetchResult(expectedFetchResponses); + final ListfetchResponsesResult = fr.getFetchResponses(); + Assert.assertEquals(fetchResponsesResult.size(), 1, "getFetchResponses() mismatched."); + Assert.assertTrue(fetchResponsesResult.get(0).keyEquals("FETCH"), "getFetchResponses() mismatched."); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java index d82e0599..1a8896bd 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/data/MessageNumberSetTest.java @@ -141,6 +141,103 @@ public void testBuildStringWith0LengthMessageNumberSets() { Assert.assertNull(MessageNumberSet.buildString(new MessageNumberSet[0]), "Result mismatched."); } + /** + * Tests buildMessageNumberSets(String) method with only one number. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithOneString() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, 1) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method having both start and end as number. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithStartEnd() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, 100) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method having start with a number and end with last. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithStartEndWithLast() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, LastMessage.LAST_MESSAGE) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method with last message only. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithLastMessageOnly() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(LastMessage.LAST_MESSAGE) }; + final String msgSetStr = MessageNumberSet.buildString(expectedMsgSets); + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method with last message only. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithEndStartWithLast() throws ImapAsyncClientException { + final MessageNumberSet[] expectedMsgSets = { new MessageNumberSet(1, LastMessage.LAST_MESSAGE) }; + final String msgSetStr = "*:1"; + final MessageNumberSet[] actualMsgSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(actualMsgSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(actualMsgSets.length, 1, "buildMessageNumberSets() size mismatched."); + Assert.assertEquals(actualMsgSets[0], expectedMsgSets[0], "buildMessageNumberSets() mismatched."); + } + + /** + * Tests buildMessageNumberSets(String) method with multiple of message number sets. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithMultipleMsgNumSets() throws ImapAsyncClientException { + final int numMsgs = 4; + final MessageNumberSet[] expectedMsgs = new MessageNumberSet[numMsgs]; + expectedMsgs[0] = new MessageNumberSet(1, 5); + expectedMsgs[1] = new MessageNumberSet(1, LastMessage.LAST_MESSAGE); + expectedMsgs[2] = new MessageNumberSet(2, 2); + expectedMsgs[3] = new MessageNumberSet(LastMessage.LAST_MESSAGE); + final String msgSetStr = MessageNumberSet.buildString(expectedMsgs); + final MessageNumberSet[] messageNumberSets = MessageNumberSet.buildMessageNumberSets(msgSetStr); + Assert.assertNotNull(messageNumberSets, "buildMessageNumberSets() should not return null."); + Assert.assertEquals(messageNumberSets.length, numMsgs, "buildMessageNumberSets() size mismatched."); + for (int i = 0; i < numMsgs; i++) { + Assert.assertEquals(messageNumberSets[i], expectedMsgs[i], "buildMessageNumberSets() mismatched."); + } + } + /** * Tests constructor and converting it to string. * @@ -219,4 +316,124 @@ public void testCommandTypeEnum() { final LastMessage value = LastMessage.valueOf("LAST_MESSAGE"); Assert.assertSame(value, LastMessage.LAST_MESSAGE, "Enum does not match."); } + + /** + * Tests BuildMessageNumberSets with bad input with three elements in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputTwoColonThreeElements() { + final String input = "1,2,1:2:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with one and two colons in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputTwoColonOneElement() { + final String input = "1,2,1::"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with only one element and one colon in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputOneColonOneElement() { + final String input = "1,2,:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with extra space. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputWithSpaceAfterColon() { + final String input = "1,2: 3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with extra space after number. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputWithSpace() { + final String input = "1 ,2:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with bad input with only one element and one colon in one sequence set. + */ + @Test + public void testBuildMessageNumberSetsWithBadInputCharacter() { + final String input = "1,2,a:3"; + ImapAsyncClientException ex = null; + try { + MessageNumberSet.buildMessageNumberSets(input); + } catch (final ImapAsyncClientException e) { + ex = e; + } + Assert.assertNotNull(ex, "IMAP Async Client Exception should be thrown."); + Assert.assertEquals(ex.getFailureType(), FailureType.INVALID_INPUT, "Wrong fail type was thrown."); + } + + /** + * Tests BuildMessageNumberSets with null input. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithNull() throws ImapAsyncClientException { + final String input = null; + final MessageNumberSet[] mns = MessageNumberSet.buildMessageNumberSets(input); + Assert.assertNull(mns, "buildMessageNumberSets() should return null"); + } + + /** + * Tests BuildMessageNumberSets with empty string input. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testBuildMessageNumberSetsWithEmptyString() throws ImapAsyncClientException { + final String input = ""; + final MessageNumberSet[] mns = MessageNumberSet.buildMessageNumberSets(input); + Assert.assertNull(mns, "buildMessageNumberSets() should return null"); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java index a44dd95d..c34d11a0 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/data/SearchResultTest.java @@ -1,6 +1,6 @@ package com.yahoo.imapnio.async.data; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.testng.Assert; @@ -16,21 +16,15 @@ public class SearchResultTest { */ @Test public void testSearchResult() { - final List ll = new ArrayList<>(); - ll.add(Long.MAX_VALUE - 1); - final SearchResult infos = new SearchResult(ll); - final List result = infos.getMessageNumbers(); - Assert.assertEquals(result.size(), 1, "Result mismatched."); - Assert.assertEquals(result.get(0), Long.valueOf(Long.MAX_VALUE - 1), "Result mismatched."); - } + final List ll = Collections.singletonList(Long.MAX_VALUE - 1); + final SearchResult sr = new SearchResult(ll, 1L); + final List result = sr.getMessageNumbers(); + final Long modSeq = sr.getHighestModSeq(); - /** - * Tests SearchResult constructor and getters when passing null list. - */ - @Test - public void testSearchResultNullList() { - final SearchResult infos = new SearchResult(null); - final List result = infos.getMessageNumbers(); - Assert.assertNull(result, "Result mismatched."); + Assert.assertNotNull(result, "getMessageNumbers() should not return null."); + Assert.assertEquals(result.size(), 1, "getMessageNumbers() size mismatched."); + Assert.assertEquals(result.get(0), Long.valueOf(Long.MAX_VALUE - 1), "getMessageNumbers() mismatched."); + Assert.assertNotNull(modSeq, "getHighestModSeq() should not return null"); + Assert.assertEquals(modSeq, Long.valueOf(1), "getHighestModSeq() mismatched."); } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java b/core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java new file mode 100644 index 00000000..5a8cb017 --- /dev/null +++ b/core/src/test/java/com/yahoo/imapnio/async/data/StoreResultTest.java @@ -0,0 +1,41 @@ +package com.yahoo.imapnio.async.data; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.IMAPResponse; + +/** + * Unit test for {@code StoreResult}. + */ +public class StoreResultTest { + /** + * Tests StoreResult constructor and getters. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testStoreResult() throws IOException, ProtocolException { + final FetchResponse fr = new FetchResponse(new IMAPResponse("* 1 FETCH (UID 4 MODSEQ (12121231000))")); + final List fetchResponses = Collections.singletonList(fr); + final MessageNumberSet[] modifiedMsgSet = { new MessageNumberSet(1L, 1L) }; + final StoreResult storeResult = new StoreResult(1L, fetchResponses, modifiedMsgSet); + final Long highestModSeq = storeResult.getHighestModSeq(); + final List responses = storeResult.getFetchResponses(); + + Assert.assertEquals(responses.size(), 1, "getFetchResponses() mismatched."); + Assert.assertNotNull(highestModSeq, "getHighestModSeq() should not return null"); + Assert.assertEquals(highestModSeq, Long.valueOf(1L), "getHighestModSeq() mismatched."); + final MessageNumberSet[] modifiedMsgsets = storeResult.getModifiedMsgSets(); + Assert.assertNotNull(modifiedMsgsets, "getModifiedMsgSets() should not return null."); + Assert.assertEquals(modifiedMsgsets.length, 1, "getModifiedMsgSets() size mismatched."); + Assert.assertEquals(modifiedMsgsets[0], modifiedMsgSet[0], "getModifiedMsgSets() mismatched."); + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java index 0d62edae..d5a8264e 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/ExamineFolderCommandTest.java @@ -120,4 +120,16 @@ public void testGetCommandLineWithQResyncParam() throws ImapAsyncClientException cmd = new ExamineFolderCommand(folderName, qResyncParameter); Assert.assertEquals(cmd.getCommandLine(), EXAMINE + "&bUuL1Q- (QRESYNC (100 4223212 1:200 (1 1:10)))\r\n", "Expected result mismatched."); } + + /** + * Tests getCommandLine method with CONDSTORE enable. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithCondStore() throws ImapAsyncClientException { + final String folderName = "测试"; + ImapRequest cmd = new ExamineFolderCommand(folderName, true); + Assert.assertEquals(cmd.getCommandLine(), EXAMINE + "&bUuL1Q- (CONDSTORE)\r\n", "getCommandLine() mismatched."); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java index 7897ab31..cd1d473c 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/FetchCommandTest.java @@ -109,6 +109,54 @@ public void testGetCommandLineWithStartEndConstructor() } } + /** + * Tests getCommandLine method using MessageNumberSet[], macro, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 4294967293L, 4294967294L, 4294967295L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new FetchCommand(msgsets, FetchMacro.FAST, 1L); + Assert.assertEquals(cmd.getCommandLine(), "FETCH 4294967293:4294967295 FAST (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], data items, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithDataItemsChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new FetchCommand(msgsets, DATA_ITEMS, 1L); + Assert.assertEquals(cmd.getCommandLine(), "FETCH 1:3 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getStreamingResponsesQueue method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java index a3b03304..b00cb426 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/ImapArgumentFormatterTest.java @@ -215,4 +215,36 @@ public void testBuildFlagStringWithUserFlagString() { Assert.assertNotNull(s, "buildFlagString() should not return null."); Assert.assertEquals(s, "(userflag1 userflag2)", "result mismatched."); } + + /** + * Tests buildEntryFlagName with all flags. + */ + @Test + public void testBuildEntryFlagName() { + final String[] expectedFlags = new String[] {"\\Answered", "\\Deleted", "\\Draft", "\\Flagged", "\\Recent", "\\Seen"}; + final Flags flags = new Flags(); + flags.add(Flags.Flag.ANSWERED); + flags.add(Flags.Flag.DELETED); + flags.add(Flags.Flag.DRAFT); + flags.add(Flags.Flag.FLAGGED); + flags.add(Flags.Flag.RECENT); + flags.add(Flags.Flag.SEEN); + flags.add("userflag1"); + final ImapArgumentFormatter writer = new ImapArgumentFormatter(); + final Flags.Flag[] systemFlags = flags.getSystemFlags(); + for (int i = 0; i < systemFlags.length; i++) { + final Flags singleSystemFlag = new Flags(); + singleSystemFlag.add(systemFlags[i]); + final String systemEntryName = writer.buildEntryFlagName(singleSystemFlag); + Assert.assertEquals(systemEntryName, "\"/flags/\\" + expectedFlags[i] + "\"", "buildEntryFlagName() mismatched."); + } + + final String[] userFlags = flags.getUserFlags(); + final Flags singleUserFlag = new Flags(); + singleUserFlag.add(userFlags[0]); + final String userEntryName = writer.buildEntryFlagName(singleUserFlag); + + Assert.assertEquals(userFlags.length, 1, "result mismatched"); + Assert.assertEquals(userEntryName, "\"/flags/userflag1\"", "buildEntryFlagName() mismatched"); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java index d5e60d09..d4b0cbb1 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/SearchCommandTest.java @@ -14,7 +14,9 @@ import java.util.Set; import javax.mail.Flags; +import javax.mail.search.BodyTerm; import javax.mail.search.FlagTerm; +import javax.mail.search.OrTerm; import javax.mail.search.SearchException; import javax.mail.search.SubjectTerm; @@ -25,6 +27,7 @@ import com.sun.mail.iap.Argument; import com.sun.mail.iap.Literal; import com.yahoo.imapnio.async.data.Capability; +import com.yahoo.imapnio.async.data.ExtendedModifiedSinceTerm; import com.yahoo.imapnio.async.data.MessageNumberSet; import com.yahoo.imapnio.async.data.MessageNumberSet.LastMessage; import com.yahoo.imapnio.async.exception.ImapAsyncClientException; @@ -237,6 +240,137 @@ public void testGetCommandLineNoneNullMessageSeqSetsNullSearchTermNullCharset() } } + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeq() throws IOException, IllegalArgumentException, IllegalAccessException, SearchException, + ImapAsyncClientException { + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeqWithFlagAnsweredTypeAll() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.ANSWERED); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.ALL, 1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ \"/flags/\\\\Answered\" ALL 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalArgumentException will not throw + * @throws IllegalAccessException will not throw + * @throws SearchException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeqWithFlagDeletedTypePriv() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.DELETED); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.PRIVATE, 1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ \"/flags/\\\\Deleted\" PRIV 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineNonNullModSeqWithFlagDraftTypeShared() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.DRAFT); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.SHARED, 1L); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, extendedModifiedSinceTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH MODSEQ \"/flags/\\\\Draft\" SHARED 1\r\n", "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with null message sequences set, MODSEQ SearchTerm with entry name and entry type. + * + * @throws IOException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + * @throws SearchException will not throw + */ + @Test + public void testGetCommandLineOrNonNullModSeqWithFlagDraftTypeShared() throws IOException, IllegalArgumentException, IllegalAccessException, + SearchException, ImapAsyncClientException { + final Flags flags = new Flags(); + flags.add(Flags.Flag.DRAFT); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(flags, EntryTypeRequest.SHARED, 1L); + final BodyTerm bodyTerm = new BodyTerm("Text"); + final OrTerm orTerm = new OrTerm(extendedModifiedSinceTerm, bodyTerm); + final MessageNumberSet[] msgsets = null; + final ImapRequest cmd = new SearchCommand(msgsets, orTerm, null); + Assert.assertEquals(cmd.getCommandLine(), "SEARCH OR MODSEQ \"/flags/\\\\Draft\" SHARED 1 BODY Text\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getCommandLine method with none-null message sequences set, null SearchTerm. * diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java index 0d2edad4..2afa9029 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/SelectFolderCommandTest.java @@ -121,4 +121,16 @@ public void testGetCommandLineWithQResyncParam() throws ImapAsyncClientException cmd = new SelectFolderCommand(folderName, qResyncParameter); Assert.assertEquals(cmd.getCommandLine(), SELECT + "&bUuL1Q- (QRESYNC (100 4223212 1:200 (1 1:10)))\r\n", "Expected result mismatched."); } + + /** + * Tests getCommandLine method with CONDSTORE enabled. + * + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithCondStore() throws ImapAsyncClientException { + final String folderName = "测试"; + ImapRequest cmd = new SelectFolderCommand(folderName, true); + Assert.assertEquals(cmd.getCommandLine(), SELECT + "&bUuL1Q- (CONDSTORE)\r\n", "getCommandLine() mismatched."); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java index 64e8b03b..24710d69 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/StoreFlagsCommandTest.java @@ -216,6 +216,85 @@ public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilent() } } + /** + * Tests getCommandLine method using message sequences, flags, adding flags, not silent, and the given unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedNotSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final ImapRequest cmd = new StoreFlagsCommand(msgsets, flags, FlagsAction.ADD, 1L); + Assert.assertEquals(cmd.getCommandLine(), "STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using message sequences, flags, adding flags, silent, and the given unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new StoreFlagsCommand(msgsets, flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with message sequences, adding flags, silent, and the given unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilentUnchangedSince() throws IllegalAccessException, ImapAsyncClientException { + + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new StoreFlagsCommand("1:*", flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "STORE 1:* (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getStreamingResponsesQueue method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java index cab47b64..e8055083 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/UidFetchCommandTest.java @@ -129,6 +129,102 @@ public void testGetCommandLineFromConstructorWithUidStringAndMacro() } } + /** + * Tests getCommandLine method using MessageNumberSet[], data items, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 1L, 2L, 3L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, DATA_ITEMS, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 1:3 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], macro, and changed since the given modification sequence. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithMacroChangedSince() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 4294967293L, 4294967294L, 4294967295L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, FetchMacro.FAST, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 4294967293:4294967295 FAST (CHANGEDSINCE 1)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], data items, changed since the given modification sequence and vanished flag. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithChangedSinceVanished() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 1L, 2L, 3L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, DATA_ITEMS, 1L, true); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 1:3 (FLAGS BODY[HEADER.FIELDS (DATE FROM)]) (CHANGEDSINCE 1 VANISHED)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using MessageNumberSet[], macro, changed since the given modification sequence and vanished flag. + * + * @throws ImapAsyncClientException will not throw + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + */ + @Test + public void testGetCommandLineFromConstructorWithMacroChangedSinceVanished() throws ImapAsyncClientException, IllegalArgumentException, + IllegalAccessException { + + final long[] msgs = { 4294967293L, 4294967294L, 4294967295L }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final ImapRequest cmd = new UidFetchCommand(msgsets, FetchMacro.FAST, 1L, true); + Assert.assertEquals(cmd.getCommandLine(), "UID FETCH 4294967293:4294967295 FAST (CHANGEDSINCE 1 VANISHED)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getCommandType method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java b/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java index 2f8fb100..11dc2a01 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/request/UidStoreFlagsCommandTest.java @@ -216,6 +216,85 @@ public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilent() } } + /** + * Tests getCommandLine method using message sequences, flags, adding flags, not silent, and unchanged since modification sequence.. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedNotSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final ImapRequest cmd = new UidStoreFlagsCommand(msgsets, flags, FlagsAction.ADD, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method using message sequences, flags, adding flags, silent, and unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws IllegalArgumentException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithFlagsAddedSilentUnchangedSince() throws IllegalArgumentException, IllegalAccessException, + ImapAsyncClientException { + + final int[] msgs = { 1, 2, 3 }; + final MessageNumberSet[] msgsets = MessageNumberSet.createMessageNumberSets(msgs); + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new UidStoreFlagsCommand(msgsets, flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID STORE 1:3 (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + + /** + * Tests getCommandLine method with message sequences, adding flags, silent, and unchanged since modification sequence. + * + * @throws IllegalAccessException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testGetCommandLineWithMessageSeqStringFlagsAddedAndSilentUnchangedSince() throws IllegalAccessException, ImapAsyncClientException { + + final Flags flags = new Flags(); + flags.add(Flags.Flag.SEEN); + flags.add(Flags.Flag.DELETED); + final boolean isSilent = true; + final ImapRequest cmd = new UidStoreFlagsCommand("1:*", flags, FlagsAction.ADD, isSilent, 1L); + Assert.assertEquals(cmd.getCommandLine(), "UID STORE 1:* (UNCHANGEDSINCE 1) +FLAGS.SILENT (\\Deleted \\Seen)\r\n", + "getCommandLine() mismatched."); + + cmd.cleanup(); + // Verify if cleanup happened correctly. + for (final Field field : fieldsToCheck) { + Assert.assertNull(field.get(cmd), "Cleanup should set " + field.getName() + " as null"); + } + } + /** * Tests getCommandType method. */ diff --git a/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java b/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java index c2a218d5..12832964 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java @@ -15,6 +15,8 @@ import com.sun.mail.iap.ProtocolException; import com.sun.mail.imap.AppendUID; import com.sun.mail.imap.CopyUID; +import com.sun.mail.imap.protocol.FetchItem; +import com.sun.mail.imap.protocol.FetchResponse; import com.sun.mail.imap.protocol.ID; import com.sun.mail.imap.protocol.IMAPResponse; import com.sun.mail.imap.protocol.ListInfo; @@ -24,10 +26,13 @@ import com.yahoo.imapnio.async.data.EnableResult; import com.yahoo.imapnio.async.data.ExtensionListInfo; import com.yahoo.imapnio.async.data.ExtensionMailboxInfo; +import com.yahoo.imapnio.async.data.FetchResult; import com.yahoo.imapnio.async.data.IdResult; import com.yahoo.imapnio.async.data.ListInfoList; import com.yahoo.imapnio.async.data.ListStatusResult; +import com.yahoo.imapnio.async.data.MessageNumberSet; import com.yahoo.imapnio.async.data.SearchResult; +import com.yahoo.imapnio.async.data.StoreResult; import com.yahoo.imapnio.async.exception.ImapAsyncClientException; import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; @@ -1013,6 +1018,7 @@ public void testParseStatusOK() throws IOException, ProtocolException, ImapAsync content.add(new IMAPResponse("* S2TATUS blurdybloop (MESSAGES 232 UIDNEXT 44293)")); content.add(new IMAPResponse("* STATUS blurdybloop (UNSEEN 4)")); content.add(new IMAPResponse("* STATUS blurdybloop (UIDVALIDITY 999333)")); + content.add(new IMAPResponse("* STATUS blurdybloop (HIGHESTMODSEQ 2483)")); content.add(new IMAPResponse("A042 OK STATUS completed")); final Status status = mapper.readValue(content.toArray(new IMAPResponse[0]), Status.class); @@ -1023,6 +1029,7 @@ public void testParseStatusOK() throws IOException, ProtocolException, ImapAsync Assert.assertEquals(status.total, 231, "total mismatched."); Assert.assertEquals(status.unseen, 4, "unseen mismatched, should take the latter one."); Assert.assertEquals(status.uidvalidity, 999333, "uidvalidity mismatched."); + Assert.assertEquals(status.highestmodseq, 2483, "highest modseq mismatched."); } /** @@ -1228,11 +1235,69 @@ public void testParseToSearchResultOK() throws IOException, ProtocolException, I // verify the result Assert.assertNotNull(result, "result mismatched."); final List list = result.getMessageNumbers(); - Assert.assertNotNull(list, "getMessageSequence() mismatched."); - Assert.assertEquals(list.size(), 3, "getMessageSequence() mismatched."); - Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageSequence() mismatched."); - Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageSequence() mismatched."); - Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageSequence() mismatched."); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNull(result.getHighestModSeq(), "getHighestModSeq() mismatch."); + } + + /** + * Tests parseSearchResult method successfully with modification sequence. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSEQ 2473)\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + final Long modSeq = result.getHighestModSeq(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNotNull(modSeq, "getHighestModSeq() should not return null."); + Assert.assertEquals(modSeq, Long.valueOf(2473), "getHighestModSeq() mismatched."); + } + + /** + * Tests parseSearchResult method successfully without correct modification sequence string. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultNoModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSE 2473)\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + final Long modSeq = result.getHighestModSeq(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNull(modSeq, "getHighestModSeq() should return null."); } /** @@ -1438,4 +1503,485 @@ public void testParseToEnableResultNotOK() throws IOException, ProtocolException Assert.assertNotNull(cause, "cause mismatched."); Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); } + /** + * Tests parseStore method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("A042 OK Store success"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + } + + /** + * Tests parseStore method successfully with CondStore. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with CondStore and wrong modified string. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreWithWrongModified() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIE 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should return null."); + } + + /** + * Tests parseStore method successfully with No response and CondStore. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHEST 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 No [MODIFIED 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNull(storeResult.getHighestModSeq(), "getHighestModSeq() should return null."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with multiple modification numbers. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreWithMultipleModified() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1:2,5:4] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + final List irs = storeResult.getFetchResponses(); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() mismatched"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 2, "getModifiedMsgsets() mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1:2,4:5", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with vanished response.. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreVanished() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2600))"); + content[3] = new IMAPResponse("* VANISHED (EARLIER) 41,43:116,118,120:211,214:540"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1,2,3] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + Assert.assertEquals(irs.size(), 2, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 3, "getModifiedMsgsets() mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1,2,3", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method with Bad response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreBad() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with No response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreNo() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with 0 response throw exception. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreZeroResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with invalid fetch response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreFailParsingFetchResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* FETCH (FLAG (\\Seen SEEN))"); + content[1] = new IMAPResponse("* OK"); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method successfully with NO response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoResponseCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("B001 NO [MODIFIED 2] Some of the messages no longer exist.") }; + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); + Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with NO response and space after left bracket. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoResponseCondStoreWithSpace() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("B001 NO [ MODIFIED 2] Some of the messages no longer exist.") }; + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); + Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("A042 OK Fetch success") }; + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + + // verify the result + Assert.assertNotNull(fetchResult, "Fetch result mismatched."); + Assert.assertEquals(fetchResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[4]; + content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[2] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[3] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "Fetch result mismatched."); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + } + + /** + * Tests parseFetch method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method with 0 response. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchZeroResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method with invalid fetch response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchFailParsingFetchResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* FETCH (FLAG (\\Seen SEEN))"); + content[1] = new IMAPResponse("* OK"); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOKCondStoreExpunge() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[6]; + content[0] = new IMAPResponse("* 1 EXPUNGE"); + content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 3 EXPUNGE"); + content[3] = new IMAPResponse("* 4 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[4] = new IMAPResponse("* 5 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[5] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "fetch result mismatched."); + Assert.assertEquals(irs.size(), 3, "fetch result mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchExtension() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[1] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class, new FetchItem[0]); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "fetch result mismatched."); + Assert.assertEquals(irs.size(), 1, "fetch result mismatched."); + } + + /** + * Tests parse a class that mapper does not support. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseClassUnknownWithFetchExtension() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, ID.class, new FetchItem[0]); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); + } } diff --git a/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java b/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java index f9c25981..993fe34c 100644 --- a/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java +++ b/core/src/test/java/com/yahoo/imapnio/command/ArgumentTest.java @@ -4,7 +4,11 @@ import javax.mail.Flags; import javax.mail.internet.MimeUtility; +import javax.mail.search.AndTerm; +import javax.mail.search.BodyTerm; import javax.mail.search.FlagTerm; +import javax.mail.search.NotTerm; +import javax.mail.search.OrTerm; import javax.mail.search.SearchException; import javax.mail.search.SubjectTerm; @@ -12,6 +16,8 @@ import org.testng.annotations.Test; import com.sun.mail.imap.protocol.SearchSequence; +import com.yahoo.imapnio.async.data.ExtendedModifiedSinceTerm; +import com.yahoo.imapnio.async.request.ExtendedSearchSequence; /** * Unit test for {@link Argument}. @@ -74,4 +80,63 @@ public void testConstructorAndToStringNoneNullCharset() throws SearchException, final String searchStr = args.toString(); Assert.assertEquals(searchStr, "SUBJECT {4+}\r\nᅫ뢔ᄅ 1:5", "result mismatched."); } -} \ No newline at end of file + + /** + * Tests constructor and toString() method with null character set and extended search sequence. + * + * @throws IOException will not throw + * @throws SearchException will not throw + */ + @Test + public void testConstructorModifiedSince() throws SearchException, IOException { + + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + final Argument args = new Argument(); + args.append(searchSeq.generateSequence(extendedModifiedSinceTerm)); + + final String searchStr = args.toString(); + Assert.assertEquals(searchStr, "MODSEQ 1", "generateSequence() mismatched."); + } + + /** + * Tests constructor and toString() method with null character set and extended search sequence with Or And search terms. + * + * @throws IOException will not throw + * @throws SearchException will not throw + */ + @Test + public void testConstructorOrAnd() throws SearchException, IOException { + + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); + final BodyTerm bodyTerm = new BodyTerm("test"); + final AndTerm andTerm = new AndTerm(bodyTerm, bodyTerm); + final OrTerm orTerm = new OrTerm(andTerm, andTerm); + final Argument args = new Argument(); + args.append(searchSeq.generateSequence(orTerm, null)); + + final String searchStr = args.toString(); + Assert.assertEquals(searchStr, "OR (BODY test BODY test) (BODY test BODY test)", "generateSequence() mismatched."); + } + + /** + * Tests constructor and toString() method with null character set and extended search sequence with Not And Modified Since terms. + * + * @throws IOException will not throw + * @throws SearchException will not throw + */ + @Test + public void testConstructorNotAndModifiedSince() throws SearchException, IOException { + + final ExtendedSearchSequence searchSeq = new ExtendedSearchSequence(); + final ExtendedModifiedSinceTerm extendedModifiedSinceTerm = new ExtendedModifiedSinceTerm(1L); + final BodyTerm bodyTerm = new BodyTerm("test"); + final AndTerm andTerm = new AndTerm(bodyTerm, extendedModifiedSinceTerm); + final NotTerm notTerm = new NotTerm(andTerm); + final Argument args = new Argument(); + args.append(searchSeq.generateSequence(notTerm)); + + final String searchStr = args.toString(); + Assert.assertEquals(searchStr, "NOT (BODY test MODSEQ 1)", "generateSequence() mismatched."); + } +} diff --git a/pom.xml b/pom.xml index abf0eaa3..9cd444b0 100644 --- a/pom.xml +++ b/pom.xml @@ -19,14 +19,8 @@ - lafa - Lafa - luis.alves@lafaspot.com - - - kraman - Kumar Raman - kumar.raman@yahoo.com + Yahoo Inc. + https://github.com/yahoo From ada9ffdc9494b1167cba31a246f9c44e4cad4f01 Mon Sep 17 00:00:00 2001 From: Alex Wang Date: Fri, 17 Apr 2020 15:14:54 -0400 Subject: [PATCH 2/2] update version --- .gitignore | 3 + README.md | 252 +- core/pom.xml | 284 +- .../async/response/ImapResponseMapper.java | 1354 +++--- .../response/ImapResponseMapperTest.java | 3974 ++++++++--------- pom.xml | 1196 ++--- 6 files changed, 3533 insertions(+), 3530 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a253de0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +html +target diff --git a/README.md b/README.md index 5a05a922..101cfa27 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,126 @@ - - -# imapnio -A Java library that supports NIO (Non-blocking I/O) based IMAP clients. - -The IMAP NIO client provides a framework to access an IMAP message store using None-blocking I/O mechanism. This design scales well for thousands of connections per machine and reduces contention when using a large number of threads and CPUs. - - -## Table of Contents - -- [Background](#background) -- [Install](#install) -- [Usage](#usage) -- [Release](#release) -- [Contribute](#contribute) -- [License](#license) - - -## Background - -The traditional accessing IMAP message store uses [JavaMail API](https://www.oracle.com/technetwork/java/javamail/index.html), which requires a blocking I/O. In this case, threads are blocked when performing I/O with the other end. This project was developed to relieve the waiting thread to perform other tasks, and it's design efficiently improves thread utilization to maximize hardware throughput and capacity. - -Some of the more distinguishing features of this library are: -- Highly customizable thread model and server/client idle max limit. -- Leverages the well-established framework [Netty](https://netty.io/) -- Future-based design enables a clean separation of IMAP client threads pool versus consumers threads pool. -- IMAPv4, [RFC3501](https://tools.ietf.org/html/rfc3501) support. -- ID command, [RFC2971](https://tools.ietf.org/html/rfc2971) support. -- IDLE command, [RFC2177](https://tools.ietf.org/html/rfc2177) support -- MOVE command, [RFC6851](https://tools.ietf.org/html/rfc6851) support -- UNSELECT command, [RFC3691](https://tools.ietf.org/html/rfc3691) support - -This project is ideal for applications that have a high requirement to optimize thread utilization and improve overall resource capacity. Specifically, this is best for situations where users perform a very high level of sessions and communication with the IMAP server. - -## Install - -This is a Java library. After downloading it, compile it using `mvn clean install` - -Then, update your project's pom.xml file dependencies, as follows: - -``` - - com.yahoo.imapnio - imapnio.core - 4.3.7 - -``` -Finally, import the relevant classes and use this library according to the usage section below. - -- For contributors run deploy to do a push to nexus servers - -``` - $ mvn clean deploy -Dgpg.passphrase=[pathPhrase] -``` - -## Usage - -The following code examples demonstrate basic functionality relate to connecting to and communicating with IMAP servers. - -### Create a client -```java - // Create a ImapAsyncClient instance with number of threads to handle the server requests - final int numOfThreadsServed = 5; - final ImapAsyncClient imapClient = new ImapAsyncClient(numOfThreads); -``` -### Establish a session with an IMAP server -```java - // Create a new session via the ImapAsyncClient instance created above and connect to that server. For the illustration purpose, - // "imaps://imap.server.com:993" is used - final URI serverUri = new URI("imaps://imap.server.com:993"); - final ImapAsyncSessionConfig config = new ImapAsyncSessionConfig(); - config.setConnectionTimeoutMillis(5000); - config.setReadTimeoutMillis(6000); - final List sniNames = null; - - final InetSocketAddress localAddress = null; - final Future future = imapClient.createSession(serverUri, config, localAddress, sniNames, DebugMode.DEBUG_OFF); - - //this version is a future-based nio client. Check whether future is done by following code. - if (future.isDone()) { - System.out.println("Future is done."); - } -``` - -### Execute the IMAP command to IMAP server -Following codes uses a Capability command as an example. - -```java - // Executes the capability command - final Future capaCmdFuture = session.execute(new CapaCommand()); - -``` - -### Handle the response from IMAP server -If the future of the executed command is done, obtain the response. -Following example shows how to read ImapAsyncResponse which wraps the content sent from the server. - -```java - if (capaCmdFuture.isDone()) { - System.out.println("Capability command is done."); - final ImapAsyncResponse resp = future.get(5, TimeUnit.MILLISECONDS); - final ImapResponseMapper mapper = new ImapResponseMapper(); - final Capability capa = mapper.readValue(resp.getResponseLines().toArray(new IMAPResponse[0]), Capability.class); - final List values = capa.getCapability("AUTH"); - } -``` - -## Release - -This release, 2.0.x, is a major release. Changes are: -- It supports non-blocking IO functionality through providing callers with java.util.concurrent.Future object. -- Listener-based non-blocking IO capability will not be supported in this release. - -## Contribute - -Please refer to the [contributing.md](Contributing.md) for information about how to get involved. We welcome issues, questions, and pull requests. Pull Requests are welcome. - - -## Maintainers - -Yahoo Inc.: https://github.com/yahoo - - -## License - -This project is licensed under the terms of the [Apache 2.0](LICENSE-Apache-2.0) open source license. Please refer to [LICENSE](LICENSE) for the full terms. + + +# imapnio +A Java library that supports NIO (Non-blocking I/O) based IMAP clients. + +The IMAP NIO client provides a framework to access an IMAP message store using None-blocking I/O mechanism. This design scales well for thousands of connections per machine and reduces contention when using a large number of threads and CPUs. + + +## Table of Contents + +- [Background](#background) +- [Install](#install) +- [Usage](#usage) +- [Release](#release) +- [Contribute](#contribute) +- [License](#license) + + +## Background + +The traditional accessing IMAP message store uses [JavaMail API](https://www.oracle.com/technetwork/java/javamail/index.html), which requires a blocking I/O. In this case, threads are blocked when performing I/O with the other end. This project was developed to relieve the waiting thread to perform other tasks, and it's design efficiently improves thread utilization to maximize hardware throughput and capacity. + +Some of the more distinguishing features of this library are: +- Highly customizable thread model and server/client idle max limit. +- Leverages the well-established framework [Netty](https://netty.io/) +- Future-based design enables a clean separation of IMAP client threads pool versus consumers threads pool. +- IMAPv4, [RFC3501](https://tools.ietf.org/html/rfc3501) support. +- ID command, [RFC2971](https://tools.ietf.org/html/rfc2971) support. +- IDLE command, [RFC2177](https://tools.ietf.org/html/rfc2177) support +- MOVE command, [RFC6851](https://tools.ietf.org/html/rfc6851) support +- UNSELECT command, [RFC3691](https://tools.ietf.org/html/rfc3691) support + +This project is ideal for applications that have a high requirement to optimize thread utilization and improve overall resource capacity. Specifically, this is best for situations where users perform a very high level of sessions and communication with the IMAP server. + +## Install + +This is a Java library. After downloading it, compile it using `mvn clean install` + +Then, update your project's pom.xml file dependencies, as follows: + +``` + + com.yahoo.imapnio + imapnio.core + 4.3.8 + +``` +Finally, import the relevant classes and use this library according to the usage section below. + +- For contributors run deploy to do a push to nexus servers + +``` + $ mvn clean deploy -Dgpg.passphrase=[pathPhrase] +``` + +## Usage + +The following code examples demonstrate basic functionality relate to connecting to and communicating with IMAP servers. + +### Create a client +```java + // Create a ImapAsyncClient instance with number of threads to handle the server requests + final int numOfThreadsServed = 5; + final ImapAsyncClient imapClient = new ImapAsyncClient(numOfThreads); +``` +### Establish a session with an IMAP server +```java + // Create a new session via the ImapAsyncClient instance created above and connect to that server. For the illustration purpose, + // "imaps://imap.server.com:993" is used + final URI serverUri = new URI("imaps://imap.server.com:993"); + final ImapAsyncSessionConfig config = new ImapAsyncSessionConfig(); + config.setConnectionTimeoutMillis(5000); + config.setReadTimeoutMillis(6000); + final List sniNames = null; + + final InetSocketAddress localAddress = null; + final Future future = imapClient.createSession(serverUri, config, localAddress, sniNames, DebugMode.DEBUG_OFF); + + //this version is a future-based nio client. Check whether future is done by following code. + if (future.isDone()) { + System.out.println("Future is done."); + } +``` + +### Execute the IMAP command to IMAP server +Following codes uses a Capability command as an example. + +```java + // Executes the capability command + final Future capaCmdFuture = session.execute(new CapaCommand()); + +``` + +### Handle the response from IMAP server +If the future of the executed command is done, obtain the response. +Following example shows how to read ImapAsyncResponse which wraps the content sent from the server. + +```java + if (capaCmdFuture.isDone()) { + System.out.println("Capability command is done."); + final ImapAsyncResponse resp = future.get(5, TimeUnit.MILLISECONDS); + final ImapResponseMapper mapper = new ImapResponseMapper(); + final Capability capa = mapper.readValue(resp.getResponseLines().toArray(new IMAPResponse[0]), Capability.class); + final List values = capa.getCapability("AUTH"); + } +``` + +## Release + +This release, 2.0.x, is a major release. Changes are: +- It supports non-blocking IO functionality through providing callers with java.util.concurrent.Future object. +- Listener-based non-blocking IO capability will not be supported in this release. + +## Contribute + +Please refer to the [contributing.md](Contributing.md) for information about how to get involved. We welcome issues, questions, and pull requests. Pull Requests are welcome. + + +## Maintainers + +Yahoo Inc.: https://github.com/yahoo + + +## License + +This project is licensed under the terms of the [Apache 2.0](LICENSE-Apache-2.0) open source license. Please refer to [LICENSE](LICENSE) for the full terms. diff --git a/core/pom.xml b/core/pom.xml index c297839b..a6e354a5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,142 +1,142 @@ - - - - - com.yahoo.imapnio - imapnio - 4.3.7 - ../pom.xml - - 4.0.0 - imapnio.core - jar - ${project.artifactId} - https://github.com/yahoo/imapnio - imapnio component ${project.name} - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - ${project.parent.basedir} - 5 - 0 - - - - - - org.testng - testng - test - - - - com.google.code.findbugs - jsr305 - - - - io.netty - netty-handler - - - com.sun.mail - javax.mail - - - org.slf4j - slf4j-api - - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - org.mockito - mockito-all - test - - - commons-codec - commons-codec - - - - - - org.jacoco - jacoco-maven-plugin - - - default-check - - check - - - - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - 0.98 - - - COMPLEXITY - COVEREDRATIO - 0.97 - - - LINE - COVEREDRATIO - 0.98 - - - CLASS - COVEREDRATIO - 1 - - - METHOD - COVEREDRATIO - 0.98 - - - BRANCH - COVEREDRATIO - 0.98 - - - CLASS - MISSEDCOUNT - 0 - - - - - - - - - - - - - - - - - + + + + + com.yahoo.imapnio + imapnio + 4.3.8 + ../pom.xml + + 4.0.0 + imapnio.core + jar + ${project.artifactId} + https://github.com/yahoo/imapnio + imapnio component ${project.name} + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + ${project.parent.basedir} + 5 + 0 + + + + + + org.testng + testng + test + + + + com.google.code.findbugs + jsr305 + + + + io.netty + netty-handler + + + com.sun.mail + javax.mail + + + org.slf4j + slf4j-api + + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.mockito + mockito-all + test + + + commons-codec + commons-codec + + + + + + org.jacoco + jacoco-maven-plugin + + + default-check + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.98 + + + COMPLEXITY + COVEREDRATIO + 0.97 + + + LINE + COVEREDRATIO + 0.98 + + + CLASS + COVEREDRATIO + 1 + + + METHOD + COVEREDRATIO + 0.98 + + + BRANCH + COVEREDRATIO + 0.98 + + + CLASS + MISSEDCOUNT + 0 + + + + + + + + + + + + + + + + + diff --git a/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java b/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java index 8476464b..6400c5c3 100644 --- a/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java +++ b/core/src/main/java/com/yahoo/imapnio/async/response/ImapResponseMapper.java @@ -1,677 +1,677 @@ -package com.yahoo.imapnio.async.response; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.mail.Folder; - -import com.sun.mail.iap.ParsingException; -import com.sun.mail.iap.ProtocolException; -import com.sun.mail.iap.Response; -import com.sun.mail.imap.AppendUID; -import com.sun.mail.imap.CopyUID; -import com.sun.mail.imap.protocol.FetchItem; -import com.sun.mail.imap.protocol.FetchResponse; -import com.sun.mail.imap.protocol.IMAPResponse; -import com.sun.mail.imap.protocol.ListInfo; -import com.sun.mail.imap.protocol.MailboxInfo; -import com.sun.mail.imap.protocol.Status; -import com.sun.mail.imap.protocol.UIDSet; -import com.yahoo.imapnio.async.data.Capability; -import com.yahoo.imapnio.async.data.EnableResult; -import com.yahoo.imapnio.async.data.ExtensionListInfo; -import com.yahoo.imapnio.async.data.ExtensionMailboxInfo; -import com.yahoo.imapnio.async.data.FetchResult; -import com.yahoo.imapnio.async.data.IdResult; -import com.yahoo.imapnio.async.data.ListInfoList; -import com.yahoo.imapnio.async.data.ListStatusResult; -import com.yahoo.imapnio.async.data.MessageNumberSet; -import com.yahoo.imapnio.async.data.SearchResult; -import com.yahoo.imapnio.async.data.StoreResult; -import com.yahoo.imapnio.async.exception.ImapAsyncClientException; -import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; - -/** - * This class parses the IMAP response to the proper IMAP object that SUN supports. - */ -public class ImapResponseMapper { - - /** APPENDUID keyword. */ - private static final String APPENDUID = "APPENDUID"; - - /** HIGHESTMODSEQ keyword. */ - private static final String HIGHESTMODSEQ = "HIGHESTMODSEQ"; - - /** MODIFIED keyword. */ - private static final String MODIFIED = "MODIFIED"; - - /** MODSEQ keyword. */ - private static final String MODSEQ = "MODSEQ"; - - /** FETCH keyword. */ - private static final String FETCH = "FETCH"; - - /** EQUAL sign. */ - private static final String EQUAL = "="; - - /** [ char. */ - private static final char L_BRACKET = '['; - - /** ] char. */ - private static final char R_BRACKET = ']'; - - /** ( char. */ - private static final char L_PAREN = '('; - - /** Inner class instance parser. */ - private ImapResponseParser parser; - - /** - * Initializes a {@link ImapResponseMapper} object. - */ - public ImapResponseMapper() { - parser = new ImapResponseParser(); - } - - /** - * Method to deserialize IMAPResponse content from given IMAPResponse content String. - * - * @param the object to serialize to - * @param content list of IMAPResponse obtained from server. - * @param valueType class name that this api will convert to. - * @return the serialized object - * @throws ImapAsyncClientException when target class to covert to is not supported - * @throws ParsingException if underlying input contains invalid content of type for the returned type - */ - @SuppressWarnings("unchecked") - @Nonnull - public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType) - throws ImapAsyncClientException, ParsingException { - if (valueType == Capability.class) { - return (T) parser.parseToCapabilities(content); - } - if (valueType == AppendUID.class) { - return (T) parser.parseToAppendUid(content); - } - if (valueType == CopyUID.class) { - return (T) parser.parseToCopyUid(content); - } - if (valueType == ExtensionMailboxInfo.class) { - return (T) parser.parseToExtensionMailboxInfo(content); - } - if (valueType == MailboxInfo.class) { - return (T) parser.parseToMailboxInfo(content); - } - if (valueType == ListInfoList.class) { - return (T) parser.parseToListInfoList(content); - } - if (valueType == Status.class) { - return (T) parser.parseToStatus(content); - } - if (valueType == IdResult.class) { - return (T) parser.parseToIdResult(content); - } - if (valueType == SearchResult.class) { - return (T) parser.parseToSearchResult(content); - } - if (valueType == ListStatusResult.class) { - return (T) parser.parseToListStatusResult(content); - } - if (valueType == EnableResult.class) { - return (T) parser.parseToEnableResult(content); - } - if (valueType == FetchResult.class) { - return (T) parser.parseToFetchResult(content, new FetchItem[0]); - } - if (valueType == StoreResult.class) { - return (T) parser.parseToStoreResult(content); - } - - throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); - } - - /** - * Method to deserialize IMAPResponse content from given IMAPResponse content String. - * - * @param the object to serialize to - * @param content list of IMAPResponse obtained from server. - * @param valueType class name that this api will convert to. - * @param extensionItems the array of extension FetchItem - * @return the serialized object - * @throws ImapAsyncClientException when target class to covert to is not supported - */ - @Nonnull - public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType, @Nonnull final FetchItem[] extensionItems) - throws ImapAsyncClientException { - if (valueType == FetchResult.class) { - return (T) parser.parseToFetchResult(content, extensionItems); - } - - throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); - } - - /** - * Inner class to perform the parsing of IMAPResponse to various objects. - */ - private class ImapResponseParser { - - /** Capability string. */ - private static final String CAPABILITY = "CAPABILITY"; - - /** - * Parses the capabilities from a CAPABILITY response or from a CAPABILITY response code attached to (e.g.) an OK response. - * - * @param rs the CAPABILITY responses - * @return a map that has the key (token) and all its values (after = sign), if there is no equal sign, value is same as key - * @throws ImapAsyncClientException when input IMAPResponse array is not valid - */ - @Nonnull - private Capability parseToCapabilities(@Nonnull final IMAPResponse[] rs) throws ImapAsyncClientException { - String s; - final Map> capas = new HashMap>(); - if (rs.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - - for (final IMAPResponse r : rs) { - if (!hasCapability(r)) { - continue; - } - while ((s = r.readAtom()) != null) { - if (s.length() == 0) { - if (r.peekByte() == (byte) R_BRACKET) { - break; - } - // Probably found something here that's not an atom. Rather than loop forever or fail completely, we'll try to skip this bogus - // capability. This is known to happen with: Netscape Messaging Server 4.03 (built Apr 27 1999) that returns: - // * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ... - // The "*" in the middle of the capability list causes us to loop forever here. - r.skipToken(); - } else { - final String[] tokens = s.split(EQUAL); - final String key = tokens[0]; - final String value = (tokens.length > 1) ? tokens[1] : null; - final String upperCase = key.toUpperCase(Locale.ENGLISH); - List values = capas.get(upperCase); - if (values == null) { - values = new ArrayList<>(); - capas.put(upperCase, values); - } - // AUTH key allows more than one pair(ex:AUTH=XOAUTH2 AUTH=PLAIN), parsing value out to List, otherwise add key to list - if (value != null) { - values.add(value); - } - } - } - } - // making the value list immutable - for (final Map.Entry> entry : capas.entrySet()) { - entry.setValue(Collections.unmodifiableList(entry.getValue())); - } - return new Capability(capas); - } - - /** - * Returns true if the response has capability keyword; false otherwise. - * - * @param r the response to check - * @return true if the response has capability keyword; false otherwise - */ - private boolean hasCapability(final IMAPResponse r) { - // case 1, from capability or authenticate command. EX: * CAPABILITY IMAP4rev1 SASL-IR - if (r.keyEquals(CAPABILITY)) { - return true; - } - - // case 2. from server greeting. EX: OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN] IMAP4rev1 Hello - byte b; - while ((b = r.readByte()) > 0 && b != (byte) '[') { - // eat chars till [ - } - if (b == 0) { // left bracket not found - return false; - } - final String s = r.readAtom(); - return s.equalsIgnoreCase(CAPABILITY); - } - - /** - * Parses APPEND response to a AppendUID instance. - * - * @param rs the APPEND responses - * @return AppendUID instance - * @throws ImapAsyncClientException when input value is not valid - */ - @Nullable - private AppendUID parseToAppendUid(@Nonnull final IMAPResponse[] rs) throws ImapAsyncClientException { - if (rs.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final IMAPResponse r = rs[rs.length - 1]; - if (!r.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - byte b; - while ((b = r.readByte()) > 0 && b != (byte) L_BRACKET) { - // eat chars till [ - } - - if (b == 0) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final String s = r.readAtom(); - if (!s.equalsIgnoreCase(APPENDUID)) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final long uidvalidity = r.readLong(); - final long uid = r.readLong(); - return new AppendUID(uidvalidity, uid); - } - - /** - * Parses COPY or MOVE command responses to a CopyUID instance. - * - * @param rr the COPY responses - * @return COPYUID instance built from copy command responses - * @throws ImapAsyncClientException when input value is not valid - */ - @Nonnull - private CopyUID parseToCopyUid(@Nonnull final IMAPResponse[] rr) throws ImapAsyncClientException { - // For copy response, it is at the last response, for move command response, it is the first response - for (int i = rr.length - 1; i >= 0; i--) { - final Response r = rr[i]; - if (r == null || !r.isOK()) { - continue; - } - byte b; - while ((b = r.readByte()) > 0 && b != (byte) L_BRACKET) { - // eat chars till [ - } - - if (b == 0) { - continue; - } - final String s = r.readAtom(); - if (!s.equalsIgnoreCase("COPYUID")) { // expunge response from MOVE, for ex: 2 EXPUNGE - continue; - } - final long uidvalidity = r.readLong(); - final String src = r.readAtom(); - final String dst = r.readAtom(); - return new CopyUID(uidvalidity, UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst)); - } - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); // when rr length is 0 - } - - /** - * Parses the SELECT or EXAMINE responses to a MailboxInfo instance. - * - * @param rr the list of responses from SELECT or EXAMINE, this input r array should contain the tagged/final one - * @return MailboxInfo instance - * @throws ParsingException when encountering parsing exception - * @throws ImapAsyncClientException when input is invalid - */ - @Nonnull - private MailboxInfo parseToMailboxInfo(@Nonnull final IMAPResponse[] rr) throws ParsingException, ImapAsyncClientException { - if (rr.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final MailboxInfo minfo = new MailboxInfo(rr); - setupMailboxInfoAccessMode(rr[rr.length - 1], minfo); - - return minfo; - } - - /** - * Sets up the mode for the {@link MailboxInfo}. - * - * @param lastResp the tagged response - * @param minfo the {@link MailboxInfo} instance - */ - private void setupMailboxInfoAccessMode(@Nonnull final Response lastResp, @Nonnull final MailboxInfo minfo) { - if (lastResp.isTagged() && lastResp.isOK()) { // command successful - if (lastResp.toString().indexOf("READ-ONLY") != -1) { - minfo.mode = Folder.READ_ONLY; - } else { - minfo.mode = Folder.READ_WRITE; - } - } - } - - /** - * Parses the SELECT or EXAMINE responses to a {@link ExtensionMailboxInfo} instance. - * - * @param rr the list of responses from SELECT or EXAMINE, this input r array should contain the tagged/final one - * @return MailboxInfo instance - * @throws ParsingException when encountering parsing exception - * @throws ImapAsyncClientException when input is invalid - */ - @Nonnull - private ExtensionMailboxInfo parseToExtensionMailboxInfo(@Nonnull final IMAPResponse[] rr) throws ParsingException, ImapAsyncClientException { - if (rr.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final ExtensionMailboxInfo minfo = new ExtensionMailboxInfo(rr); - setupMailboxInfoAccessMode(rr[rr.length - 1], minfo); - return minfo; - } - - /** - * Parses the LIST or LSUB responses to a {@link ListInfo} list. List responses example: - * - *
-         * * LIST () "/" INBOX
-         * * LIST (\NoSelect) "/" "Public mailboxes"
-         * 
- * - * @param r the list of responses from SELECT, the input responses array should contain the tagged/final one - * @return list of ListInfo objects - * @throws ParsingException when encountering parsing exception - * @throws ImapAsyncClientException when input value is not valid - */ - @Nonnull - private ListInfoList parseToListInfoList(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException { - if (r.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response response = r[r.length - 1]; - if (!response.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - - // command successful reaching here - final List v = new ArrayList(); - for (int i = 0, len = r.length - 1; i < len; i++) { - final IMAPResponse ir = r[i]; - if (ir.keyEquals("LIST") || ir.keyEquals("LSUB")) { - v.add(new ListInfo(ir)); - } - } - - // could be an empty list if the search criteria ends up no result. Ex: - // a002 LIST "" "*t3*" - // a002 OK LIST completed - return new ListInfoList(v); - } - - /** - * Parses the Status responses to a {@link Status}. - * - * @param r the list of responses from Status command, the input responses array should contain the tagged/final one - * @return Status object constructed based on the r array - * @throws ParsingException when encountering parsing exception - * @throws ImapAsyncClientException when input value is not valid - */ - @Nonnull - private Status parseToStatus(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException { - if (r.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = r[r.length - 1]; - if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - Status status = null; - for (int i = 0, len = r.length; i < len; i++) { - final IMAPResponse ir = r[i]; - if (ir.keyEquals("STATUS")) { - if (status == null) { - status = new Status(ir); - } else { // collect them all if each attributes comes in its own line - Status.add(status, new Status(ir)); - } - } - } - if (status == null) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); // when r length is 0 or no Status response - } - return status; - } - - /** - * Parses the LIST-STATUS command responses to a ListStatusResult object. See RFC 5819 for details. For each selectable mailbox matching the - * list pattern and selection options, the server MUST return an untagged LIST response followed by an untagged STATUS response containing the - * information requested in the STATUS return option. If an attempted STATUS for a listed mailbox fails because the mailbox can't be selected - * , the STATUS response MUST NOT be returned and the LIST response MUST include the \NoSelect attribute. - * - * @param r the list of responses from Status command, the input responses array should contain the tagged/final one - * @return Status object constructed based on the r array - * @throws ParsingException when encountering parsing exception - * @throws ImapAsyncClientException when input value is not valid - */ - @Nonnull - private ListStatusResult parseToListStatusResult(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException { - if (r.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = r[r.length - 1]; - if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - // command successful reaching here - final List v = new ArrayList(); - final Map m = new HashMap(); - - for (int i = 0; i < r.length; i++) { - final IMAPResponse ir = r[i]; - - if (ir.keyEquals("LIST")) { - v.add(new ExtensionListInfo(ir)); - } else if (ir.keyEquals("STATUS")) { - final Status status = new Status(ir); - m.put(status.mbox, status); - } - } - - return new ListStatusResult(v, m); - } - - /** - * Parses the ID responses to a {@link IdResult} object. - * - * @param ir the list of responses from ID command, the input responses array should contain the tagged/final one - * @return IdResult object constructed based on the given IMAPResponse array - * @throws ImapAsyncClientException when input value is not valid - */ - @Nonnull - private IdResult parseToIdResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException { - if (ir.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = ir[ir.length - 1]; - if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - - final Map serverParams = new HashMap(); - for (int j = 0, len = ir.length; j < len; j++) { - final IMAPResponse r = ir[j]; - if (r.keyEquals("ID")) { - r.skipSpaces(); - int c = r.peekByte(); - if (c == 'N' || c == 'n') { // assume NIL - return new IdResult(Collections.unmodifiableMap(Collections.EMPTY_MAP)); - } - - final String[] v = r.readStringList(); - if (v == null) { - // this means it does not start with (, ID result is expected to have () enclosed - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - - for (int i = 0; i < v.length; i += 2) { - final String name = v[i]; - if (name == null || (i + 1 >= v.length)) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final String value = v[i + 1]; - serverParams.put(name, value); - } - } - } - - return new IdResult(Collections.unmodifiableMap(serverParams)); - } - - /** - * Parses the Enable responses to a {@link EnableResult} object. - * - * @param ir the list of responses from ENABLE command, the input responses array should contain the tagged/final one - * @return EnableResult object constructed based on the given IMAPResponse array - * @throws ImapAsyncClientException when input value is not valid - */ - @Nonnull - private EnableResult parseToEnableResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException { - if (ir.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = ir[ir.length - 1]; - if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - - final Set enabledCapabilities = new HashSet(); - for (final IMAPResponse resp : ir) { - if (resp.isTagged()) { - continue; - } - if (resp.keyEquals("ENABLED")) { - String s; - while ((s = resp.readAtom()) != null && s.length() != 0) { - enabledCapabilities.add(s.toUpperCase()); - } - } - } - - return new EnableResult(enabledCapabilities); - } - - /** - * Parses the responses from UID search command to a {@link SearchResult} object. - * - * @param ir the list of responses from UID search command, the input responses array should contain the tagged/final one - * @return SearchResult object constructed based on the given IMAPResponse array - * @throws ImapAsyncClientException when tagged response is not OK or given response length is 0 - */ - @Nonnull - private SearchResult parseToSearchResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException { - if (ir.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = ir[ir.length - 1]; - if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final List v = new ArrayList(); // will always return a non-null array - - Long modSeq = null; - - // Grab all SEARCH responses - long num; - for (final IMAPResponse sr : ir) { - // There *will* be one SEARCH response. - if (sr.keyEquals("SEARCH")) { - while ((num = sr.readLong()) != -1) { - v.add(Long.valueOf(num)); - } - if (sr.readByte() == L_PAREN) { - final String s = sr.readAtom(); - if (MODSEQ.equals(s)) { - modSeq = sr.readLong(); - } - } - } - } - - return new SearchResult(v, modSeq); - } - - /** - * Parses the responses from Store command and UID Store command to a {@link StoreResult} object. - * - * @param ir the list of responses from Store or UID Store command, the input responses array should contain the tagged/final one - * @return StoreResult object constructed based on the given IMAPResponse array, - * @throws ImapAsyncClientException when tagged response is bad or given response length is 0 or fail to parse Fetch Response - */ - @Nonnull - private StoreResult parseToStoreResult(@Nonnull final IMAPResponse[] ir) - throws ImapAsyncClientException { - if (ir.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = ir[ir.length - 1]; - taggedResponse.skipSpaces(); - MessageNumberSet[] modifiedMsgsets = null; // only one response will contain modified numbers - if (!taggedResponse.isBAD() && taggedResponse.readByte() == (byte) L_BRACKET && MODIFIED.equals(taggedResponse.readAtom())) { - // ex: d105 OK [MODIFIED 7,9] Conditional STORE failed - modifiedMsgsets = MessageNumberSet.buildMessageNumberSets(taggedResponse.readAtom()); - } else if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - - final List fetchResponses = new ArrayList<>(); // will always return a non-null array - - Long highestModSeq = null; - - for (final IMAPResponse sr: ir) { - sr.skipSpaces(); - if (sr.keyEquals(FETCH)) { - try { - fetchResponses.add(new FetchResponse(sr)); - } catch (final IOException | ProtocolException e) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - } else if (sr.readByte() == (byte) L_BRACKET) { // HIGHESTMODSEQ or MODIFIED - final String responseCode = sr.readAtom(); - if (HIGHESTMODSEQ.equals(responseCode)) { - highestModSeq = sr.readLong(); - } - } - } - - return new StoreResult(highestModSeq, fetchResponses, modifiedMsgsets); - } - - /** - * Parses the responses from Fetch command and UID Fetch command to a {@link FetchResult} object. - * - * @param ir the list of responses from Fetch or UID Fetch command, the input responses array should contain the tagged/final one - * @param extensionItems the array of extension FetchItem - * @return FetchResult object constructed based on the given IMAPResponse array, - * @throws ImapAsyncClientException when tagged response is not OK or given response length is 0 or fail to parse Fetch Response - */ - @Nonnull - private FetchResult parseToFetchResult(@Nonnull final IMAPResponse[] ir, @Nonnull final FetchItem[] extensionItems) - throws ImapAsyncClientException { - if (ir.length < 1) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final Response taggedResponse = ir[ir.length - 1]; - if (!taggedResponse.isOK()) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - final List fetchResponses = new ArrayList<>(); // will always return a non-null array - - for (final IMAPResponse sr: ir) { - if (sr.keyEquals(FETCH)) { - try { - fetchResponses.add(new FetchResponse(sr, extensionItems)); - } catch (final IOException | ProtocolException e) { - throw new ImapAsyncClientException(FailureType.INVALID_INPUT); - } - } - } - - return new FetchResult(fetchResponses); - } - } -} +package com.yahoo.imapnio.async.response; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.mail.Folder; + +import com.sun.mail.iap.ParsingException; +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.iap.Response; +import com.sun.mail.imap.AppendUID; +import com.sun.mail.imap.CopyUID; +import com.sun.mail.imap.protocol.FetchItem; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.IMAPResponse; +import com.sun.mail.imap.protocol.ListInfo; +import com.sun.mail.imap.protocol.MailboxInfo; +import com.sun.mail.imap.protocol.Status; +import com.sun.mail.imap.protocol.UIDSet; +import com.yahoo.imapnio.async.data.Capability; +import com.yahoo.imapnio.async.data.EnableResult; +import com.yahoo.imapnio.async.data.ExtensionListInfo; +import com.yahoo.imapnio.async.data.ExtensionMailboxInfo; +import com.yahoo.imapnio.async.data.FetchResult; +import com.yahoo.imapnio.async.data.IdResult; +import com.yahoo.imapnio.async.data.ListInfoList; +import com.yahoo.imapnio.async.data.ListStatusResult; +import com.yahoo.imapnio.async.data.MessageNumberSet; +import com.yahoo.imapnio.async.data.SearchResult; +import com.yahoo.imapnio.async.data.StoreResult; +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; + +/** + * This class parses the IMAP response to the proper IMAP object that SUN supports. + */ +public class ImapResponseMapper { + + /** APPENDUID keyword. */ + private static final String APPENDUID = "APPENDUID"; + + /** HIGHESTMODSEQ keyword. */ + private static final String HIGHESTMODSEQ = "HIGHESTMODSEQ"; + + /** MODIFIED keyword. */ + private static final String MODIFIED = "MODIFIED"; + + /** MODSEQ keyword. */ + private static final String MODSEQ = "MODSEQ"; + + /** FETCH keyword. */ + private static final String FETCH = "FETCH"; + + /** EQUAL sign. */ + private static final String EQUAL = "="; + + /** [ char. */ + private static final char L_BRACKET = '['; + + /** ] char. */ + private static final char R_BRACKET = ']'; + + /** ( char. */ + private static final char L_PAREN = '('; + + /** Inner class instance parser. */ + private ImapResponseParser parser; + + /** + * Initializes a {@link ImapResponseMapper} object. + */ + public ImapResponseMapper() { + parser = new ImapResponseParser(); + } + + /** + * Method to deserialize IMAPResponse content from given IMAPResponse content String. + * + * @param the object to serialize to + * @param content list of IMAPResponse obtained from server. + * @param valueType class name that this api will convert to. + * @return the serialized object + * @throws ImapAsyncClientException when target class to covert to is not supported + * @throws ParsingException if underlying input contains invalid content of type for the returned type + */ + @SuppressWarnings("unchecked") + @Nonnull + public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType) + throws ImapAsyncClientException, ParsingException { + if (valueType == Capability.class) { + return (T) parser.parseToCapabilities(content); + } + if (valueType == AppendUID.class) { + return (T) parser.parseToAppendUid(content); + } + if (valueType == CopyUID.class) { + return (T) parser.parseToCopyUid(content); + } + if (valueType == ExtensionMailboxInfo.class) { + return (T) parser.parseToExtensionMailboxInfo(content); + } + if (valueType == MailboxInfo.class) { + return (T) parser.parseToMailboxInfo(content); + } + if (valueType == ListInfoList.class) { + return (T) parser.parseToListInfoList(content); + } + if (valueType == Status.class) { + return (T) parser.parseToStatus(content); + } + if (valueType == IdResult.class) { + return (T) parser.parseToIdResult(content); + } + if (valueType == SearchResult.class) { + return (T) parser.parseToSearchResult(content); + } + if (valueType == ListStatusResult.class) { + return (T) parser.parseToListStatusResult(content); + } + if (valueType == EnableResult.class) { + return (T) parser.parseToEnableResult(content); + } + if (valueType == FetchResult.class) { + return (T) parser.parseToFetchResult(content, new FetchItem[0]); + } + if (valueType == StoreResult.class) { + return (T) parser.parseToStoreResult(content); + } + + throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); + } + + /** + * Method to deserialize IMAPResponse content from given IMAPResponse content String. + * + * @param the object to serialize to + * @param content list of IMAPResponse obtained from server. + * @param valueType class name that this api will convert to. + * @param extensionItems the array of extension FetchItem + * @return the serialized object + * @throws ImapAsyncClientException when target class to covert to is not supported + */ + @Nonnull + public T readValue(@Nonnull final IMAPResponse[] content, @Nonnull final Class valueType, @Nonnull final FetchItem[] extensionItems) + throws ImapAsyncClientException { + if (valueType == FetchResult.class) { + return (T) parser.parseToFetchResult(content, extensionItems); + } + + throw new ImapAsyncClientException(FailureType.UNKNOWN_PARSE_RESULT_TYPE); + } + + /** + * Inner class to perform the parsing of IMAPResponse to various objects. + */ + private class ImapResponseParser { + + /** Capability string. */ + private static final String CAPABILITY = "CAPABILITY"; + + /** + * Parses the capabilities from a CAPABILITY response or from a CAPABILITY response code attached to (e.g.) an OK response. + * + * @param rs the CAPABILITY responses + * @return a map that has the key (token) and all its values (after = sign), if there is no equal sign, value is same as key + * @throws ImapAsyncClientException when input IMAPResponse array is not valid + */ + @Nonnull + private Capability parseToCapabilities(@Nonnull final IMAPResponse[] rs) throws ImapAsyncClientException { + String s; + final Map> capas = new HashMap>(); + if (rs.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + for (final IMAPResponse r : rs) { + if (!hasCapability(r)) { + continue; + } + while ((s = r.readAtom()) != null) { + if (s.length() == 0) { + if (r.peekByte() == (byte) R_BRACKET) { + break; + } + // Probably found something here that's not an atom. Rather than loop forever or fail completely, we'll try to skip this bogus + // capability. This is known to happen with: Netscape Messaging Server 4.03 (built Apr 27 1999) that returns: + // * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ... + // The "*" in the middle of the capability list causes us to loop forever here. + r.skipToken(); + } else { + final String[] tokens = s.split(EQUAL); + final String key = tokens[0]; + final String value = (tokens.length > 1) ? tokens[1] : null; + final String upperCase = key.toUpperCase(Locale.ENGLISH); + List values = capas.get(upperCase); + if (values == null) { + values = new ArrayList<>(); + capas.put(upperCase, values); + } + // AUTH key allows more than one pair(ex:AUTH=XOAUTH2 AUTH=PLAIN), parsing value out to List, otherwise add key to list + if (value != null) { + values.add(value); + } + } + } + } + // making the value list immutable + for (final Map.Entry> entry : capas.entrySet()) { + entry.setValue(Collections.unmodifiableList(entry.getValue())); + } + return new Capability(capas); + } + + /** + * Returns true if the response has capability keyword; false otherwise. + * + * @param r the response to check + * @return true if the response has capability keyword; false otherwise + */ + private boolean hasCapability(final IMAPResponse r) { + // case 1, from capability or authenticate command. EX: * CAPABILITY IMAP4rev1 SASL-IR + if (r.keyEquals(CAPABILITY)) { + return true; + } + + // case 2. from server greeting. EX: OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN] IMAP4rev1 Hello + byte b; + while ((b = r.readByte()) > 0 && b != (byte) '[') { + // eat chars till [ + } + if (b == 0) { // left bracket not found + return false; + } + final String s = r.readAtom(); + return s.equalsIgnoreCase(CAPABILITY); + } + + /** + * Parses APPEND response to a AppendUID instance. + * + * @param rs the APPEND responses + * @return AppendUID instance + * @throws ImapAsyncClientException when input value is not valid + */ + @Nullable + private AppendUID parseToAppendUid(@Nonnull final IMAPResponse[] rs) throws ImapAsyncClientException { + if (rs.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final IMAPResponse r = rs[rs.length - 1]; + if (!r.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + byte b; + while ((b = r.readByte()) > 0 && b != (byte) L_BRACKET) { + // eat chars till [ + } + + if (b == 0) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final String s = r.readAtom(); + if (!s.equalsIgnoreCase(APPENDUID)) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final long uidvalidity = r.readLong(); + final long uid = r.readLong(); + return new AppendUID(uidvalidity, uid); + } + + /** + * Parses COPY or MOVE command responses to a CopyUID instance. + * + * @param rr the COPY responses + * @return COPYUID instance built from copy command responses + * @throws ImapAsyncClientException when input value is not valid + */ + @Nonnull + private CopyUID parseToCopyUid(@Nonnull final IMAPResponse[] rr) throws ImapAsyncClientException { + // For copy response, it is at the last response, for move command response, it is the first response + for (int i = rr.length - 1; i >= 0; i--) { + final Response r = rr[i]; + if (r == null || !r.isOK()) { + continue; + } + byte b; + while ((b = r.readByte()) > 0 && b != (byte) L_BRACKET) { + // eat chars till [ + } + + if (b == 0) { + continue; + } + final String s = r.readAtom(); + if (!s.equalsIgnoreCase("COPYUID")) { // expunge response from MOVE, for ex: 2 EXPUNGE + continue; + } + final long uidvalidity = r.readLong(); + final String src = r.readAtom(); + final String dst = r.readAtom(); + return new CopyUID(uidvalidity, UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst)); + } + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); // when rr length is 0 + } + + /** + * Parses the SELECT or EXAMINE responses to a MailboxInfo instance. + * + * @param rr the list of responses from SELECT or EXAMINE, this input r array should contain the tagged/final one + * @return MailboxInfo instance + * @throws ParsingException when encountering parsing exception + * @throws ImapAsyncClientException when input is invalid + */ + @Nonnull + private MailboxInfo parseToMailboxInfo(@Nonnull final IMAPResponse[] rr) throws ParsingException, ImapAsyncClientException { + if (rr.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final MailboxInfo minfo = new MailboxInfo(rr); + setupMailboxInfoAccessMode(rr[rr.length - 1], minfo); + + return minfo; + } + + /** + * Sets up the mode for the {@link MailboxInfo}. + * + * @param lastResp the tagged response + * @param minfo the {@link MailboxInfo} instance + */ + private void setupMailboxInfoAccessMode(@Nonnull final Response lastResp, @Nonnull final MailboxInfo minfo) { + if (lastResp.isTagged() && lastResp.isOK()) { // command successful + if (lastResp.toString().indexOf("READ-ONLY") != -1) { + minfo.mode = Folder.READ_ONLY; + } else { + minfo.mode = Folder.READ_WRITE; + } + } + } + + /** + * Parses the SELECT or EXAMINE responses to a {@link ExtensionMailboxInfo} instance. + * + * @param rr the list of responses from SELECT or EXAMINE, this input r array should contain the tagged/final one + * @return MailboxInfo instance + * @throws ParsingException when encountering parsing exception + * @throws ImapAsyncClientException when input is invalid + */ + @Nonnull + private ExtensionMailboxInfo parseToExtensionMailboxInfo(@Nonnull final IMAPResponse[] rr) throws ParsingException, ImapAsyncClientException { + if (rr.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final ExtensionMailboxInfo minfo = new ExtensionMailboxInfo(rr); + setupMailboxInfoAccessMode(rr[rr.length - 1], minfo); + return minfo; + } + + /** + * Parses the LIST or LSUB responses to a {@link ListInfo} list. List responses example: + * + *
+         * * LIST () "/" INBOX
+         * * LIST (\NoSelect) "/" "Public mailboxes"
+         * 
+ * + * @param r the list of responses from SELECT, the input responses array should contain the tagged/final one + * @return list of ListInfo objects + * @throws ParsingException when encountering parsing exception + * @throws ImapAsyncClientException when input value is not valid + */ + @Nonnull + private ListInfoList parseToListInfoList(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException { + if (r.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response response = r[r.length - 1]; + if (!response.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + // command successful reaching here + final List v = new ArrayList(); + for (int i = 0, len = r.length - 1; i < len; i++) { + final IMAPResponse ir = r[i]; + if (ir.keyEquals("LIST") || ir.keyEquals("LSUB")) { + v.add(new ListInfo(ir)); + } + } + + // could be an empty list if the search criteria ends up no result. Ex: + // a002 LIST "" "*t3*" + // a002 OK LIST completed + return new ListInfoList(v); + } + + /** + * Parses the Status responses to a {@link Status}. + * + * @param r the list of responses from Status command, the input responses array should contain the tagged/final one + * @return Status object constructed based on the r array + * @throws ParsingException when encountering parsing exception + * @throws ImapAsyncClientException when input value is not valid + */ + @Nonnull + private Status parseToStatus(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException { + if (r.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = r[r.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + Status status = null; + for (int i = 0, len = r.length; i < len; i++) { + final IMAPResponse ir = r[i]; + if (ir.keyEquals("STATUS")) { + if (status == null) { + status = new Status(ir); + } else { // collect them all if each attributes comes in its own line + Status.add(status, new Status(ir)); + } + } + } + if (status == null) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); // when r length is 0 or no Status response + } + return status; + } + + /** + * Parses the LIST-STATUS command responses to a ListStatusResult object. See RFC 5819 for details. For each selectable mailbox matching the + * list pattern and selection options, the server MUST return an untagged LIST response followed by an untagged STATUS response containing the + * information requested in the STATUS return option. If an attempted STATUS for a listed mailbox fails because the mailbox can't be selected + * , the STATUS response MUST NOT be returned and the LIST response MUST include the \NoSelect attribute. + * + * @param r the list of responses from Status command, the input responses array should contain the tagged/final one + * @return Status object constructed based on the r array + * @throws ParsingException when encountering parsing exception + * @throws ImapAsyncClientException when input value is not valid + */ + @Nonnull + private ListStatusResult parseToListStatusResult(@Nonnull final IMAPResponse[] r) throws ParsingException, ImapAsyncClientException { + if (r.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = r[r.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + // command successful reaching here + final List v = new ArrayList(); + final Map m = new HashMap(); + + for (int i = 0; i < r.length; i++) { + final IMAPResponse ir = r[i]; + + if (ir.keyEquals("LIST")) { + v.add(new ExtensionListInfo(ir)); + } else if (ir.keyEquals("STATUS")) { + final Status status = new Status(ir); + m.put(status.mbox, status); + } + } + + return new ListStatusResult(v, m); + } + + /** + * Parses the ID responses to a {@link IdResult} object. + * + * @param ir the list of responses from ID command, the input responses array should contain the tagged/final one + * @return IdResult object constructed based on the given IMAPResponse array + * @throws ImapAsyncClientException when input value is not valid + */ + @Nonnull + private IdResult parseToIdResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + final Map serverParams = new HashMap(); + for (int j = 0, len = ir.length; j < len; j++) { + final IMAPResponse r = ir[j]; + if (r.keyEquals("ID")) { + r.skipSpaces(); + int c = r.peekByte(); + if (c == 'N' || c == 'n') { // assume NIL + return new IdResult(Collections.unmodifiableMap(Collections.EMPTY_MAP)); + } + + final String[] v = r.readStringList(); + if (v == null) { + // this means it does not start with (, ID result is expected to have () enclosed + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + for (int i = 0; i < v.length; i += 2) { + final String name = v[i]; + if (name == null || (i + 1 >= v.length)) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final String value = v[i + 1]; + serverParams.put(name, value); + } + } + } + + return new IdResult(Collections.unmodifiableMap(serverParams)); + } + + /** + * Parses the Enable responses to a {@link EnableResult} object. + * + * @param ir the list of responses from ENABLE command, the input responses array should contain the tagged/final one + * @return EnableResult object constructed based on the given IMAPResponse array + * @throws ImapAsyncClientException when input value is not valid + */ + @Nonnull + private EnableResult parseToEnableResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + final Set enabledCapabilities = new HashSet(); + for (final IMAPResponse resp : ir) { + if (resp.isTagged()) { + continue; + } + if (resp.keyEquals("ENABLED")) { + String s; + while ((s = resp.readAtom()) != null && s.length() != 0) { + enabledCapabilities.add(s.toUpperCase()); + } + } + } + + return new EnableResult(enabledCapabilities); + } + + /** + * Parses the responses from UID search command to a {@link SearchResult} object. + * + * @param ir the list of responses from UID search command, the input responses array should contain the tagged/final one + * @return SearchResult object constructed based on the given IMAPResponse array + * @throws ImapAsyncClientException when tagged response is not OK or given response length is 0 + */ + @Nonnull + private SearchResult parseToSearchResult(@Nonnull final IMAPResponse[] ir) throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final List v = new ArrayList(); // will always return a non-null array + + Long modSeq = null; + + // Grab all SEARCH responses + long num; + for (final IMAPResponse sr : ir) { + // There *will* be one SEARCH response. + if (sr.keyEquals("SEARCH")) { + while ((num = sr.readLong()) != -1) { + v.add(Long.valueOf(num)); + } + if (sr.readByte() == L_PAREN) { + final String s = sr.readAtom(); + if (MODSEQ.equals(s)) { + modSeq = sr.readLong(); + } + } + } + } + + return new SearchResult(v, modSeq); + } + + /** + * Parses the responses from Store command and UID Store command to a {@link StoreResult} object. + * + * @param ir the list of responses from Store or UID Store command, the input responses array should contain the tagged/final one + * @return StoreResult object constructed based on the given IMAPResponse array, + * @throws ImapAsyncClientException when tagged response is bad or given response length is 0 or fail to parse Fetch Response + */ + @Nonnull + private StoreResult parseToStoreResult(@Nonnull final IMAPResponse[] ir) + throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + taggedResponse.skipSpaces(); + MessageNumberSet[] modifiedMsgsets = null; // only one response will contain modified numbers + if (!taggedResponse.isBAD() && taggedResponse.readByte() == (byte) L_BRACKET && MODIFIED.equals(taggedResponse.readAtom())) { + // ex: d105 OK [MODIFIED 7,9] Conditional STORE failed + modifiedMsgsets = MessageNumberSet.buildMessageNumberSets(taggedResponse.readAtom()); + } else if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + + final List fetchResponses = new ArrayList<>(); // will always return a non-null array + + Long highestModSeq = null; + + for (final IMAPResponse sr: ir) { + sr.skipSpaces(); + if (sr.keyEquals(FETCH)) { + try { + fetchResponses.add(new FetchResponse(sr)); + } catch (final IOException | ProtocolException e) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + } else if (sr.readByte() == (byte) L_BRACKET) { // HIGHESTMODSEQ or MODIFIED + final String responseCode = sr.readAtom(); + if (HIGHESTMODSEQ.equals(responseCode)) { + highestModSeq = sr.readLong(); + } + } + } + + return new StoreResult(highestModSeq, fetchResponses, modifiedMsgsets); + } + + /** + * Parses the responses from Fetch command and UID Fetch command to a {@link FetchResult} object. + * + * @param ir the list of responses from Fetch or UID Fetch command, the input responses array should contain the tagged/final one + * @param extensionItems the array of extension FetchItem + * @return FetchResult object constructed based on the given IMAPResponse array, + * @throws ImapAsyncClientException when tagged response is not OK or given response length is 0 or fail to parse Fetch Response + */ + @Nonnull + private FetchResult parseToFetchResult(@Nonnull final IMAPResponse[] ir, @Nonnull final FetchItem[] extensionItems) + throws ImapAsyncClientException { + if (ir.length < 1) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final Response taggedResponse = ir[ir.length - 1]; + if (!taggedResponse.isOK()) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + final List fetchResponses = new ArrayList<>(); // will always return a non-null array + + for (final IMAPResponse sr: ir) { + if (sr.keyEquals(FETCH)) { + try { + fetchResponses.add(new FetchResponse(sr, extensionItems)); + } catch (final IOException | ProtocolException e) { + throw new ImapAsyncClientException(FailureType.INVALID_INPUT); + } + } + } + + return new FetchResult(fetchResponses); + } + } +} diff --git a/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java b/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java index 12832964..d28f406a 100644 --- a/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java +++ b/core/src/test/java/com/yahoo/imapnio/async/response/ImapResponseMapperTest.java @@ -1,1987 +1,1987 @@ -package com.yahoo.imapnio.async.response; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.mail.Flags.Flag; -import javax.mail.Folder; - -import org.testng.Assert; -import org.testng.annotations.Test; - -import com.sun.mail.iap.ProtocolException; -import com.sun.mail.imap.AppendUID; -import com.sun.mail.imap.CopyUID; -import com.sun.mail.imap.protocol.FetchItem; -import com.sun.mail.imap.protocol.FetchResponse; -import com.sun.mail.imap.protocol.ID; -import com.sun.mail.imap.protocol.IMAPResponse; -import com.sun.mail.imap.protocol.ListInfo; -import com.sun.mail.imap.protocol.MailboxInfo; -import com.sun.mail.imap.protocol.Status; -import com.yahoo.imapnio.async.data.Capability; -import com.yahoo.imapnio.async.data.EnableResult; -import com.yahoo.imapnio.async.data.ExtensionListInfo; -import com.yahoo.imapnio.async.data.ExtensionMailboxInfo; -import com.yahoo.imapnio.async.data.FetchResult; -import com.yahoo.imapnio.async.data.IdResult; -import com.yahoo.imapnio.async.data.ListInfoList; -import com.yahoo.imapnio.async.data.ListStatusResult; -import com.yahoo.imapnio.async.data.MessageNumberSet; -import com.yahoo.imapnio.async.data.SearchResult; -import com.yahoo.imapnio.async.data.StoreResult; -import com.yahoo.imapnio.async.exception.ImapAsyncClientException; -import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; - -/** - * Unit test for {@link ImapResponseMapper}. - */ -public class ImapResponseMapperTest { - /** Imap server greeting. */ - private static final String GREETING = "* OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE " - + "XYMHIGHESTMODSEQ UIDPLUS LITERAL+ CHILDREN X-MSG-EXT] IMAP4rev1 Hello"; - - /** - * Tests parseToCapabilities method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToCapabilitiesFromCapaCommand() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE\r\n")); - content.add(new IMAPResponse("* more junks\r\n")); - content.add(new IMAPResponse("a1 OK CAPABILITY completed\r\n")); - final Capability capa = mapper.readValue(content.toArray(new IMAPResponse[0]), Capability.class); - - // verify the result - Assert.assertNotNull(capa, "result should never return null."); - Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); - Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); - final List authValues = capa.getCapability("AUTH"); - Assert.assertNotNull(authValues, "AUTH values missed."); - Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); - Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); - Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); - Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); - } - - /** - * Tests parseToCapabilities method when ImapResponse array has zero length. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToCapabilitiesArrayLengthZero() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = {}; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Capability.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToCapabilities method successfully from an OK response that has Capability response attached to. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToCapabilitiesFromGreeting() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse(GREETING) }; - final Capability capa = mapper.readValue(content, Capability.class); - - // verify the result - Assert.assertNotNull(capa, "result should never return null."); - Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); - Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("X-MSG-EXT"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("LITERAL+"), "One capability missed."); - final List authValues = capa.getCapability("AUTH"); - Assert.assertNotNull(authValues, "AUTH values missed."); - Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); - Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); - Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); - Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); - } - - /** - * Tests parseToCapabilities method successfully when it has Netscape Messaging Server response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToCapabilitiesSkipStar() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { - new IMAPResponse("* CAPABILITY * IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE") }; - final Capability capa = mapper.readValue(content, Capability.class); - - // verify the result - Assert.assertTrue(capa.hasCapability("IMAP4rev1"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); - Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); - final List authValues = capa.getCapability("AUTH"); - Assert.assertNotNull(authValues, "AUTH values missed."); - Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); - Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); - Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); - Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); - } - - /** - * Tests parseCopyUid successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseCopyUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [COPYUID 1549405125 150395 3]"); // a good response - content[1] = new IMAPResponse("* BAD Some junks"); // test if it skips the bad response - content[2] = null; // test if it skips null - content[3] = new IMAPResponse("* OK [SOMETHING 111 222 3]"); // test if it detects it is not COPYUID keyword - content[4] = new IMAPResponse("* OK"); // test when b is 0 - - final CopyUID copyUid = mapper.readValue(content, CopyUID.class); - - // verify the result - Assert.assertNotNull(copyUid, "result mismatched."); - Assert.assertEquals(copyUid.uidvalidity, 1549405125, "result mismatched."); - Assert.assertNotNull(copyUid.src, "result mismatched."); - Assert.assertNotNull(copyUid.dst, "result mismatched."); - Assert.assertEquals(copyUid.src.length, 1, "result mismatched."); - Assert.assertEquals(copyUid.src[0].start, 150395, "result mismatched."); - Assert.assertEquals(copyUid.src[0].end, 150395, "result mismatched."); - Assert.assertEquals(copyUid.dst.length, 1, "result mismatched."); - Assert.assertEquals(copyUid.dst[0].start, 3, "result mismatched."); - Assert.assertEquals(copyUid.dst[0].end, 3, "result mismatched."); - } - - /** - * Tests ImapResponseParse parseCopyUid when Responses array is empty. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseCopyUidResponseArrayZero() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, CopyUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseAppendUid successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseAppendUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[3]; - content[0] = new IMAPResponse("+ Ready for literal data"); - content[1] = new IMAPResponse("* 3 EXISTS"); - content[2] = new IMAPResponse("a5 OK [APPENDUID 1459808247 150399] APPEND completed"); - final AppendUID appendUid = mapper.readValue(content, AppendUID.class); - - // verify the result - Assert.assertNotNull(appendUid, "result mismatched."); - Assert.assertEquals(appendUid.uidvalidity, 1459808247, "result mismatched."); - Assert.assertEquals(appendUid.uid, 150399, "result mismatched."); - } - - /** - * Tests parseToCapabilities method when ImapResponse array has zero length. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToAppendUidsArrayLengthZero() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = {}; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseAppendUid with a BAD response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseAppendUidNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("* BAD Some junks") }; - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseAppendUid when b is 0, or when left bracket is not found. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseAppendUidByteReadExhausted() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("* OK") }; // test when b is 0 - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseAppendUid when b is 0. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseAppendUidNoAppendUidKeyword() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("* OK [appendButNotUid 111 222 3]") }; // test when b is 0 - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, AppendUID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseMailboxInfo method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[8]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); - final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - } - - /** - * Tests parseExtensionMailboxInfo method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseExtensionMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[9]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("* OK [MAILBOXID (A26)] Ok"); - content[8] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); - final ExtensionMailboxInfo minfo = mapper.readValue(content, ExtensionMailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - Assert.assertEquals(minfo.getMailboxId(), "A26", "MailboxId mismatched."); - } - - /** - * Tests parseMailboxInfo method successfully with READ-WRITE mode. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseMailboxInfoReadWriteSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[8]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("002 OK [READ-WRITE] EXAMINE completed; now in selected state"); - final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, Folder.READ_WRITE, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - } - - /** - * Tests parseMailboxInfo method successfully with READ-WRITE mode. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseMailboxInfoBad() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[8]; - content[0] = new IMAPResponse("* 3 EXISTS"); - content[1] = new IMAPResponse("* 0 RECENT"); - content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); - content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); - content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); - content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); - content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); - content[7] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode - final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); - - // verify the result - Assert.assertNotNull(minfo, "result mismatched."); - Assert.assertEquals(minfo.mode, 0, "mode mismatched."); - Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); - Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); - Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); - Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); - Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); - } - - /** - * Tests parseMailboxInfo method successfully with READ-WRITE mode. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseMailboxInfoResponseArray0() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, MailboxInfo.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Adds the data for test input. - * - * @param rr output parameter, list of IMAPResponse - * @param expectedNames output parameter, list of expected folder names - * @param respStr response string - * @param folder folder name - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - private void buildListInfoIMAPResponse(final List rr, final List expectedNames, final String respStr, final String folder) - throws IOException, ProtocolException { - expectedNames.add(folder); - rr.add(new IMAPResponse(respStr + " \"" + folder + "\"\r\n")); - } - - /** - * Tests parseListInfos method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListInfosSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - // add a non-relevant response - content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); - buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "Archive"); - content.add(new IMAPResponse("* some junks\r\n")); - buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); - buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "Draft"); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "Inbox"); - buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "Sent"); - buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "Trash"); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "test1"); - content.add(new IMAPResponse("* 115141 EXISTS\r\n")); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "test1/test1_1"); - content.add(new IMAPResponse("a3 OK LIST completed")); - final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - final List infos = ll.getListInfo(); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); - Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); - for (int i = 0; i < infos.size(); i++) { - final ListInfo info = infos.get(i); - final String expectedFolder = names.get(i); - Assert.assertNotNull(info, "ListInfo should not be null."); - Assert.assertNotNull(expectedFolder, "folder name should not be null."); - Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); - Assert.assertNotNull(info.name, "Name mismatched."); - Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); - } - } - - /** - * Tests parseListInfos method successfully when responses are results of LSUB command. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListInfosFromLSubSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Archive \\HasNoChildren) \"/\"", "Archive"); - content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); - content.add(new IMAPResponse("* MORE JUNKS\r\n")); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Drafts \\HasNoChildren) \"/\"", "Draft"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "Inbox"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Sent \\HasNoChildren) \"/\"", "Sent"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\Trash \\HasNoChildren) \"/\"", "Trash"); - buildListInfoIMAPResponse(content, names, "* LSUB (\\HasChildren) \"/\"", "test1"); - content.add(new IMAPResponse("* 115141 EXISTS\r\n")); - buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "test1/test1_1"); - content.add(new IMAPResponse("a3 OK LSUB completed")); - final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - final List infos = ll.getListInfo(); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); - Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); - for (int i = 0; i < infos.size(); i++) { - final ListInfo info = infos.get(i); - final String expectedFolder = names.get(i); - Assert.assertNotNull(info, "ListInfo should not be null."); - Assert.assertNotNull(expectedFolder, "folder name should not be null."); - Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); - Assert.assertNotNull(info.name, "Name mismatched."); - Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); - } - } - - /** - * Tests parseListInfos method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseListInfosNoOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); - content.add(new IMAPResponse("a3 BAD LIST completed")); - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseListInfos method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListInfosOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List rr = new ArrayList<>(); - rr.add(new IMAPResponse("a3 OK LIST completed")); - final ListInfoList infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListInfoList.class); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - } - - /** - * Tests parseListInfos method when response array length is 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseListInfosEmptyResponses() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseListStatus method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListStatusSuccess() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List expectedLinfo = new ArrayList<>(); - final List expectedStatuses = new ArrayList<>(); - - // 0 - final String lresp0 = "* LIST (\\HasNoChildren) \"/\" \"INBOX\""; - content.add(new IMAPResponse(lresp0)); - // add some non-relevant responses - content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); - content.add(new IMAPResponse("* 115141 EXPUNGE\r\n")); - final String sresp0 = "* STATUS \"INBOX\" (HIGHESTMODSEQ 82676 MESSAGES 774 UIDNEXT 913 UIDVALIDITY 1 UNSEEN 769)"; - popluateContentAndBuildStatus(lresp0, sresp0, content, expectedLinfo, expectedStatuses); - - // 1 - content.add(new IMAPResponse("* 115142 EXPUNGE\r\n")); - final String lresp1 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"[Zmail]\""; - content.add(new IMAPResponse(lresp1)); - content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); - popluateContentAndBuildStatus(lresp1, null, content, expectedLinfo, expectedStatuses); // status is null - - // 2 - final String lresp2 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/All Mail\""; - content.add(new IMAPResponse(lresp2)); - final String sresp2 = "* STATUS \"[Zmail]/All Mail\" (HIGHESTMODSEQ 82676 MESSAGES 777 UIDNEXT 1109 UIDVALIDITY 12 UNSEEN 770)"; - popluateContentAndBuildStatus(lresp2, sresp2, content, expectedLinfo, expectedStatuses); - - // 3 - final String lresp3 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Drafts\""; - content.add(new IMAPResponse(lresp3)); - final String sresp3 = "* STATUS \"[Zmail]/Drafts\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 98 UIDVALIDITY 6 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp3, sresp3, content, expectedLinfo, expectedStatuses); - - // 4 - final String lresp4 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Important\""; - content.add(new IMAPResponse(lresp4)); - final String sresp4 = "* STATUS \"[Zmail]/Important\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 118 UIDVALIDITY 9 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp4, sresp4, content, expectedLinfo, expectedStatuses); - - // 5 - final String lresp5 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Sent Mail\""; - content.add(new IMAPResponse(lresp5)); - final String sresp5 = "* STATUS \"[Zmail]/Sent Mail\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 88 UIDVALIDITY 5 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp5, sresp5, content, expectedLinfo, expectedStatuses); - - // 6 - final String lresp6 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Spam\""; - content.add(new IMAPResponse(lresp6)); - final String sresp6 = "* STATUS \"[Zmail]/Spam\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 101 UIDVALIDITY 3 UNSEEN 1)"; - popluateContentAndBuildStatus(lresp6, sresp6, content, expectedLinfo, expectedStatuses); - - // 7 - final String lresp7 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Starred\""; - content.add(new IMAPResponse(lresp7)); - final String sresp7 = "* STATUS \"[Zmail]/Starred\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 9 UIDVALIDITY 4 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp7, sresp7, content, expectedLinfo, expectedStatuses); - - // 8 - final String lresp8 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Trash\""; - content.add(new IMAPResponse(lresp8)); - final String sresp8 = "* STATUS \"[Zmail]/Trash\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 137 UIDVALIDITY 2 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp8, sresp8, content, expectedLinfo, expectedStatuses); - - // 9 - final String lresp9 = "* LIST (\\HasChildren) \"/\" \"parent_folder\""; - content.add(new IMAPResponse(lresp9)); - final String sresp9 = "* STATUS \"parent_folder\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 1 UIDVALIDITY 15 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp9, sresp9, content, expectedLinfo, expectedStatuses); - - // 10 - final String lresp10 = "* LIST (\\HasNoChildren) \"/\" \"parent_folder/child_folder\""; - content.add(new IMAPResponse(lresp10)); - final String sresp10 = "* STATUS \"parent_folder/child_folder\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 2 UIDVALIDITY 16 UNSEEN 0)"; - popluateContentAndBuildStatus(lresp10, sresp10, content, expectedLinfo, expectedStatuses); - - // 11 - final String lresp11 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"abc_folder\""; - content.add(new IMAPResponse(lresp11)); - content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); - popluateContentAndBuildStatus(lresp11, null, content, expectedLinfo, expectedStatuses); // status is null - - content.add(new IMAPResponse("a3 OK Success")); - - final ListStatusResult ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); - - // verify the result - final List infos = ll.getListInfos(); - Assert.assertNotNull(infos, "result mismatched."); - Assert.assertEquals(infos.size(), 12, "ListInfo count mismatched."); - final Map statuses = ll.getStatuses(); - - for (int i = 0; i < infos.size(); i++) { - final ExtensionListInfo info = infos.get(i); - final ExtensionListInfo expectedInfo = expectedLinfo.get(i); - final Status expectedStatus = expectedStatuses.get(i); - - Assert.assertNotNull(info, "ListStatus should not be null."); - - // verify ListInfo - Assert.assertNotNull(info, "ListInfo should not be null."); - Assert.assertEquals(info.getAvailableExtendedAttributes(), expectedInfo.getAvailableExtendedAttributes(), "Data mismatched."); - - Assert.assertEquals(info.hasInferiors, expectedInfo.hasInferiors, "hasInferiors mismatched."); - Assert.assertEquals(info.name, expectedInfo.name, "ListInfo name mismatched."); - Assert.assertEquals(info.attrs.length, expectedInfo.attrs.length, "ListInfo attrs size mismatched."); - for (int j = 0; j < expectedInfo.attrs.length; j++) { - Assert.assertEquals(info.attrs[j], expectedInfo.attrs[j], "info.attrs[j] mismatched."); - } - final Status st = statuses.get(info.name); - - // Verify Status - if (expectedStatus == null) { // if expecting no status for this folder - Assert.assertNull(st, "Expected Status mismatched."); - continue; - } - Assert.assertEquals(st.mbox, expectedStatus.mbox, "Status.mbox mismatched."); - Assert.assertEquals(info.name, st.mbox, "ListInfo and Status mismatched. "); - Assert.assertEquals(st.highestmodseq, expectedStatus.highestmodseq, "highestmodseq mismatched."); - Assert.assertEquals(st.total, expectedStatus.total, "total mismatched."); - Assert.assertEquals(st.recent, expectedStatus.recent, "recent mismatched."); - Assert.assertEquals(st.uidnext, expectedStatus.uidnext, "highestmodseq mismatched."); - Assert.assertEquals(st.uidvalidity, expectedStatus.uidvalidity, "highestmodseq mismatched."); - Assert.assertEquals(st.unseen, expectedStatus.unseen, "highestmodseq mismatched."); - Assert.assertEquals(st.items, expectedStatus.items, "items mismatched."); - if (expectedStatus.items != null) { - Assert.assertEquals(st.items.size(), expectedStatus.items.size(), "items mismatched."); - for (final String itemName : expectedStatus.items.keySet()) { - Assert.assertEquals(st.items.get(itemName), expectedStatus.items.get(itemName), "Item value mismatched."); - } - } - } - } - - /** - * Builds the Status for expected Status and populate given Content for input. - * - * @param listResp List response in string format - * @param statusResp Status response in string format - * @param content IMAPResponse list - * @param expectedInfo expected ListInfo list - * @param expectedSt expected Status list - * @return - * @throws ProtocolException will not throw - * @throws IOException will not throw - */ - private void popluateContentAndBuildStatus(final String listResp, final String statusResp, final List content, - final List expectedInfo, final List expectedSt) throws IOException, ProtocolException { - - expectedInfo.add(new ExtensionListInfo(new IMAPResponse(listResp))); - if (statusResp != null) { - content.add(new IMAPResponse(statusResp)); - expectedSt.add(new Status(new IMAPResponse(statusResp))); - } else { - expectedSt.add(null); - } - } - - /** - * Tests parseListInfos method when response array length is 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseListStausEmptyResponses() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseListInfos method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseListStatusNoOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - final List names = new ArrayList<>(); - buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); - buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); - content.add(new IMAPResponse("a3 BAD LIST completed")); - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - - } - - /** - * Tests parseListStatus method when final response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseListStatusOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List rr = new ArrayList<>(); - rr.add(new IMAPResponse("a3 OK LIST completed")); - final ListStatusResult infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListStatusResult.class); - - // verify the result - Assert.assertNotNull(infos, "result mismatched."); - } - - /** - * Tests ExtensionMailboxInfo method when response array length is 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseMailboxExtensionInfosEmptyResponses() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), ExtensionMailboxInfo.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStatus method with response array 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseStatusArray0() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Status.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parse a class that mapper does not support. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseClassUnknown() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, ID.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); - } - - /** - * Tests parseStatus method with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStatusNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Status.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStatus method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStatusNoStatusResponse() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* NOSTATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)"); - content[1] = new IMAPResponse("A042 OK STATUS completed"); - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, Status.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStatus method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStatusOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292 UNSEEN 3)")); - content.add(new IMAPResponse("* S2TATUS blurdybloop (MESSAGES 232 UIDNEXT 44293)")); - content.add(new IMAPResponse("* STATUS blurdybloop (UNSEEN 4)")); - content.add(new IMAPResponse("* STATUS blurdybloop (UIDVALIDITY 999333)")); - content.add(new IMAPResponse("* STATUS blurdybloop (HIGHESTMODSEQ 2483)")); - content.add(new IMAPResponse("A042 OK STATUS completed")); - - final Status status = mapper.readValue(content.toArray(new IMAPResponse[0]), Status.class); - - // verify the result - Assert.assertNotNull(status, "status mismatched."); - Assert.assertEquals(status.uidnext, 44292, "uidnext mismatched."); - Assert.assertEquals(status.total, 231, "total mismatched."); - Assert.assertEquals(status.unseen, 4, "unseen mismatched, should take the latter one."); - Assert.assertEquals(status.uidvalidity, 999333, "uidvalidity mismatched."); - Assert.assertEquals(status.highestmodseq, 2483, "highest modseq mismatched."); - } - - /** - * Tests parseToID method with response array 0. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultArray0() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToID method with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToIdResult with first byte is N. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultFirstByteIsN() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID NIL \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - final IdResult id = mapper.readValue(content, IdResult.class); - - // verify the result - Assert.assertNotNull(id, "id mismatched."); - Assert.assertFalse(id.hasKey("name"), "name key mismatched."); - Assert.assertNull(id.getValue("name"), "name value mismatched."); - } - - /** - * Tests parseToIdResult with first byte is n. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultFirstByteIsLowercaseN() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID nIL \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - final IdResult id = mapper.readValue(content, IdResult.class); - - // verify the result - Assert.assertNotNull(id, "id mismatched."); - Assert.assertFalse(id.hasKey("name"), "name key mismatched."); - Assert.assertNull(id.getValue("name"), "name value mismatched."); - } - - /** - * Tests parseToIdResult with first byte not left parenthesis. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultNotStartWithLeftParenthesis() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID X \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToIdResult with first byte not left parenthesis. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultNameAbsent() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - // sun java mail library 1.5.5 has bug fix in readStringList() to return 0 length array instead of 1 length with a null element - - content[0] = new IMAPResponse("* ID () \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - // verify the result - final IdResult id = mapper.readValue(content, IdResult.class); - // verify the result - Assert.assertNotNull(id, "result mismatched."); - } - - /** - * Tests parseToIdResult with first byte not left parenthesis. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToIdResultValueAbsent() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* ID (') \n"); - content[1] = new IMAPResponse("a042 OK ID command completed"); - - // verify the result - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, IdResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToID method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToIdResultOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[3]; - content[0] = new IMAPResponse("* ID (\"name\" \"Cyrus\" \"version\" \"1.5\" \"os\" \"sunos\"\n\"os-version\" \"5.5\" \"support-url\"\n" - + "\"mailto:cyrus-bugs+@andrew.cmu.edu\")\n"); - content[1] = new IMAPResponse("junk"); - content[2] = new IMAPResponse("a042 OK ID command completed"); - - final IdResult id = mapper.readValue(content, IdResult.class); - - // verify the result - Assert.assertNotNull(id, "id mismatched."); - Assert.assertTrue(id.hasKey("name"), "name should be present."); - Assert.assertEquals(id.getValue("name"), "Cyrus", "name value mismatched."); - } - - /** - * Tests parseSearchResult method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToSearchResultOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* SEARCH 150404 150406 150407\r\n"); - content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); - - final SearchResult result = mapper.readValue(content, SearchResult.class); - - // verify the result - Assert.assertNotNull(result, "result mismatched."); - final List list = result.getMessageNumbers(); - Assert.assertNotNull(list, "getMessageNumbers() mismatched."); - Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); - Assert.assertNull(result.getHighestModSeq(), "getHighestModSeq() mismatch."); - } - - /** - * Tests parseSearchResult method successfully with modification sequence. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToSearchResultModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSEQ 2473)\r\n"); - content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); - - final SearchResult result = mapper.readValue(content, SearchResult.class); - - // verify the result - Assert.assertNotNull(result, "result mismatched."); - final List list = result.getMessageNumbers(); - final Long modSeq = result.getHighestModSeq(); - Assert.assertNotNull(list, "getMessageNumbers() mismatched."); - Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); - Assert.assertNotNull(modSeq, "getHighestModSeq() should not return null."); - Assert.assertEquals(modSeq, Long.valueOf(2473), "getHighestModSeq() mismatched."); - } - - /** - * Tests parseSearchResult method successfully without correct modification sequence string. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToSearchResultNoModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSE 2473)\r\n"); - content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); - - final SearchResult result = mapper.readValue(content, SearchResult.class); - - // verify the result - Assert.assertNotNull(result, "result mismatched."); - final List list = result.getMessageNumbers(); - final Long modSeq = result.getHighestModSeq(); - Assert.assertNotNull(list, "getMessageNumbers() mismatched."); - Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); - Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); - Assert.assertNull(modSeq, "getHighestModSeq() should return null."); - } - - /** - * Tests parseSearchResult method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToSearchResultOKNoSearchResult() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* SEARCH\r\n"); - content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); - - final SearchResult result = mapper.readValue(content, SearchResult.class); - - // verify the result - Assert.assertNotNull(result, "result mismatched."); - final List list = result.getMessageNumbers(); - Assert.assertNotNull(list, "getMessageSequence() mismatched."); - Assert.assertEquals(list.size(), 0, "getMessageSequence() mismatched."); - } - - /** - * Tests parseToSearchResult method when tagged response is not OK. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseToSearchResultZeroLengthResponse() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - - ImapAsyncClientException actual = null; - try { - final SearchResult result = mapper.readValue(content, SearchResult.class); - } catch (final ImapAsyncClientException e) { - actual = e; - } - // verify the result - Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); - Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToSearchResult method when tagged response is not OK. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseToSearchResultNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("a3 BAD SEARCH completed (Failure)\r\n"); - - ImapAsyncClientException actual = null; - try { - final SearchResult result = mapper.readValue(content, SearchResult.class); - } catch (final ImapAsyncClientException e) { - actual = e; - } - // verify the result - Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); - Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToEnableResult method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResult() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 2, "capability missed."); - Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); - Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); - } - - /** - * Tests parseToEnableResult method with response contains length zero line. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultLengthZero() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); - content.add(new IMAPResponse("")); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 2, "capability missed."); - Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); - Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); - } - - /** - * Tests parseToEnableResult method with response contains only * ENABLED but no capability. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNoCapa() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* ENABLED\r\n")); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 0, "capability missed."); - } - - /** - * Tests parseToEnableResult method when server does not enable all the capability. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNotEnabled() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); - final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - - // verify the result - Assert.assertNotNull(enableResult, "result should never return null."); - final Set capas = enableResult.getEnabledCapabilities(); - Assert.assertEquals(capas.size(), 0, "capability missed."); - } - - /** - * Tests parseToEnableResult method when server returns with no response contents. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNoContent() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseToEnableResult method when server returns with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseToEnableResultNotOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final List content = new ArrayList<>(); - content.add(new IMAPResponse("* some junks\r\n")); - content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); - content.add(new IMAPResponse("a3 BAD ENABLE fail\r\n")); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - /** - * Tests parseStore method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("A042 OK Store success"); - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result mismatched."); - Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); - } - - /** - * Tests parseStore method successfully with CondStore. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); - content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); - content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); - content[4] = new IMAPResponse("a4 OK [MODIFIED 1] Conditional Store Failed (Success)"); - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result should not be null."); - Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); - Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); - final List irs = storeResult.getFetchResponses(); - Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); - Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); - final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); - Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); - } - - /** - * Tests parseStore method successfully with CondStore and wrong modified string. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreOKCondStoreWithWrongModified() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); - content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); - content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); - content[4] = new IMAPResponse("a4 OK [MODIFIE 1] Conditional Store Failed (Success)"); - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result should not be null."); - Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); - Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); - final List irs = storeResult.getFetchResponses(); - Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should return null."); - } - - /** - * Tests parseStore method successfully with No response and CondStore. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreNoCondStore() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [HIGHEST 2682"); - content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); - content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); - content[4] = new IMAPResponse("a4 No [MODIFIED 1] Conditional Store Failed (Success)"); - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result should not be null."); - Assert.assertNull(storeResult.getHighestModSeq(), "getHighestModSeq() should return null."); - final List irs = storeResult.getFetchResponses(); - Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); - Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); - Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); - final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); - Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); - } - - /** - * Tests parseStore method successfully with multiple modification numbers. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreOKCondStoreWithMultipleModified() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); - content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); - content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); - content[4] = new IMAPResponse("a4 OK [MODIFIED 1:2,5:4] Conditional Store Failed (Success)"); - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result should not be null."); - final List irs = storeResult.getFetchResponses(); - Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); - Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); - Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); - Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() mismatched"); - Assert.assertEquals(storeResult.getModifiedMsgSets().length, 2, "getModifiedMsgsets() mismatched"); - final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); - Assert.assertEquals(msgSets, "1:2,4:5", "getModifiedMsgsets() mismatched."); - } - - /** - * Tests parseStore method successfully with vanished response.. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreOKCondStoreVanished() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[5]; - content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); - content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2600))"); - content[3] = new IMAPResponse("* VANISHED (EARLIER) 41,43:116,118,120:211,214:540"); - content[4] = new IMAPResponse("a4 OK [MODIFIED 1,2,3] Conditional Store Failed (Success)"); - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result mismatched."); - final List irs = storeResult.getFetchResponses(); - Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); - Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); - Assert.assertEquals(irs.size(), 2, "getFetchResponses() mismatched."); - Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); - Assert.assertEquals(storeResult.getModifiedMsgSets().length, 3, "getModifiedMsgsets() mismatched"); - final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); - Assert.assertEquals(msgSets, "1,2,3", "getModifiedMsgsets() mismatched."); - } - - /** - * Tests parseStore method with Bad response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStoreBad() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, StoreResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStore method with No response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStoreNo() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, StoreResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStore method with 0 response throw exception. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseStoreZeroResponse() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, StoreResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStore method with invalid fetch response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseStoreFailParsingFetchResponse() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* FETCH (FLAG (\\Seen SEEN))"); - content[1] = new IMAPResponse("* OK"); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, StoreResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseStore method successfully with NO response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreNoResponseCondStore() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("B001 NO [MODIFIED 2] Some of the messages no longer exist.") }; - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result mismatched."); - Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); - Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); - Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); - Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); - } - - /** - * Tests parseStore method successfully with NO response and space after left bracket. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseStoreNoResponseCondStoreWithSpace() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("B001 NO [ MODIFIED 2] Some of the messages no longer exist.") }; - final StoreResult storeResult = mapper.readValue(content, StoreResult.class); - - // verify the result - Assert.assertNotNull(storeResult, "store result mismatched."); - Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); - Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); - Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); - Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); - } - - /** - * Tests parseFetch method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseFetchOK() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("A042 OK Fetch success") }; - final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); - - // verify the result - Assert.assertNotNull(fetchResult, "Fetch result mismatched."); - Assert.assertEquals(fetchResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); - } - - /** - * Tests parseFetch method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseFetchOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[4]; - content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); - content[2] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); - content[3] = new IMAPResponse("a4 OK Success"); - final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); - final List irs = fetchResult.getFetchResponses(); - - // verify the result - Assert.assertNotNull(fetchResult, "Fetch result mismatched."); - Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); - } - - /** - * Tests parseFetch method with not OK response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseFetchNotOK() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, FetchResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseFetch method with 0 response. - * - * @throws ProtocolException will not throw - */ - @Test - public void testParseFetchZeroResponse() throws ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[0]; - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, FetchResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseFetch method with invalid fetch response. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseFetchFailParsingFetchResponse() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* FETCH (FLAG (\\Seen SEEN))"); - content[1] = new IMAPResponse("* OK"); - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, FetchResult.class); - } catch (final ImapAsyncClientException e) { - cause = e; - } - - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); - } - - /** - * Tests parseFetch method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseFetchOKCondStoreExpunge() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[6]; - content[0] = new IMAPResponse("* 1 EXPUNGE"); - content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[2] = new IMAPResponse("* 3 EXPUNGE"); - content[3] = new IMAPResponse("* 4 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); - content[4] = new IMAPResponse("* 5 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); - content[5] = new IMAPResponse("a4 OK Success"); - final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); - final List irs = fetchResult.getFetchResponses(); - - // verify the result - Assert.assertNotNull(fetchResult, "fetch result mismatched."); - Assert.assertEquals(irs.size(), 3, "fetch result mismatched."); - } - - /** - * Tests parseFetch method successfully. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - * @throws ImapAsyncClientException will not throw - */ - @Test - public void testParseFetchExtension() throws IOException, ProtocolException, ImapAsyncClientException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[2]; - content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); - content[1] = new IMAPResponse("a4 OK Success"); - final FetchResult fetchResult = mapper.readValue(content, FetchResult.class, new FetchItem[0]); - final List irs = fetchResult.getFetchResponses(); - - // verify the result - Assert.assertNotNull(fetchResult, "fetch result mismatched."); - Assert.assertEquals(irs.size(), 1, "fetch result mismatched."); - } - - /** - * Tests parse a class that mapper does not support. - * - * @throws IOException will not throw - * @throws ProtocolException will not throw - */ - @Test - public void testParseClassUnknownWithFetchExtension() throws IOException, ProtocolException { - final ImapResponseMapper mapper = new ImapResponseMapper(); - final IMAPResponse[] content = new IMAPResponse[1]; - content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode - - ImapAsyncClientException cause = null; - try { - mapper.readValue(content, ID.class, new FetchItem[0]); - } catch (final ImapAsyncClientException e) { - cause = e; - } - // verify the result - Assert.assertNotNull(cause, "cause mismatched."); - Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); - } -} +package com.yahoo.imapnio.async.response; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.mail.Flags.Flag; +import javax.mail.Folder; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.sun.mail.iap.ProtocolException; +import com.sun.mail.imap.AppendUID; +import com.sun.mail.imap.CopyUID; +import com.sun.mail.imap.protocol.FetchItem; +import com.sun.mail.imap.protocol.FetchResponse; +import com.sun.mail.imap.protocol.ID; +import com.sun.mail.imap.protocol.IMAPResponse; +import com.sun.mail.imap.protocol.ListInfo; +import com.sun.mail.imap.protocol.MailboxInfo; +import com.sun.mail.imap.protocol.Status; +import com.yahoo.imapnio.async.data.Capability; +import com.yahoo.imapnio.async.data.EnableResult; +import com.yahoo.imapnio.async.data.ExtensionListInfo; +import com.yahoo.imapnio.async.data.ExtensionMailboxInfo; +import com.yahoo.imapnio.async.data.FetchResult; +import com.yahoo.imapnio.async.data.IdResult; +import com.yahoo.imapnio.async.data.ListInfoList; +import com.yahoo.imapnio.async.data.ListStatusResult; +import com.yahoo.imapnio.async.data.MessageNumberSet; +import com.yahoo.imapnio.async.data.SearchResult; +import com.yahoo.imapnio.async.data.StoreResult; +import com.yahoo.imapnio.async.exception.ImapAsyncClientException; +import com.yahoo.imapnio.async.exception.ImapAsyncClientException.FailureType; + +/** + * Unit test for {@link ImapResponseMapper}. + */ +public class ImapResponseMapperTest { + /** Imap server greeting. */ + private static final String GREETING = "* OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE " + + "XYMHIGHESTMODSEQ UIDPLUS LITERAL+ CHILDREN X-MSG-EXT] IMAP4rev1 Hello"; + + /** + * Tests parseToCapabilities method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToCapabilitiesFromCapaCommand() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE\r\n")); + content.add(new IMAPResponse("* more junks\r\n")); + content.add(new IMAPResponse("a1 OK CAPABILITY completed\r\n")); + final Capability capa = mapper.readValue(content.toArray(new IMAPResponse[0]), Capability.class); + + // verify the result + Assert.assertNotNull(capa, "result should never return null."); + Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); + Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); + final List authValues = capa.getCapability("AUTH"); + Assert.assertNotNull(authValues, "AUTH values missed."); + Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); + Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); + Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); + Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); + } + + /** + * Tests parseToCapabilities method when ImapResponse array has zero length. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToCapabilitiesArrayLengthZero() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = {}; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Capability.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToCapabilities method successfully from an OK response that has Capability response attached to. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToCapabilitiesFromGreeting() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse(GREETING) }; + final Capability capa = mapper.readValue(content, Capability.class); + + // verify the result + Assert.assertNotNull(capa, "result should never return null."); + Assert.assertTrue(capa.hasCapability("IMAP4rev1".toUpperCase()), "One capability missed."); + Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("X-MSG-EXT"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("LITERAL+"), "One capability missed."); + final List authValues = capa.getCapability("AUTH"); + Assert.assertNotNull(authValues, "AUTH values missed."); + Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); + Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); + Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); + Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); + } + + /** + * Tests parseToCapabilities method successfully when it has Netscape Messaging Server response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToCapabilitiesSkipStar() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { + new IMAPResponse("* CAPABILITY * IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=XOAUTH2 AUTH=OAUTHBEARER ID MOVE NAMESPACE") }; + final Capability capa = mapper.readValue(content, Capability.class); + + // verify the result + Assert.assertTrue(capa.hasCapability("IMAP4rev1"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("SASL-IR"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("ID"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("MOVE"), "One capability missed."); + Assert.assertTrue(capa.hasCapability("NAMESPACE"), "One capability missed."); + final List authValues = capa.getCapability("AUTH"); + Assert.assertNotNull(authValues, "AUTH values missed."); + Assert.assertEquals(authValues.size(), 3, "One Auth value missed"); + Assert.assertEquals(authValues.get(0), "PLAIN", "One Auth value missed"); + Assert.assertEquals(authValues.get(1), "XOAUTH2", "One Auth value missed"); + Assert.assertEquals(authValues.get(2), "OAUTHBEARER", "One Auth value missed"); + } + + /** + * Tests parseCopyUid successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseCopyUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [COPYUID 1549405125 150395 3]"); // a good response + content[1] = new IMAPResponse("* BAD Some junks"); // test if it skips the bad response + content[2] = null; // test if it skips null + content[3] = new IMAPResponse("* OK [SOMETHING 111 222 3]"); // test if it detects it is not COPYUID keyword + content[4] = new IMAPResponse("* OK"); // test when b is 0 + + final CopyUID copyUid = mapper.readValue(content, CopyUID.class); + + // verify the result + Assert.assertNotNull(copyUid, "result mismatched."); + Assert.assertEquals(copyUid.uidvalidity, 1549405125, "result mismatched."); + Assert.assertNotNull(copyUid.src, "result mismatched."); + Assert.assertNotNull(copyUid.dst, "result mismatched."); + Assert.assertEquals(copyUid.src.length, 1, "result mismatched."); + Assert.assertEquals(copyUid.src[0].start, 150395, "result mismatched."); + Assert.assertEquals(copyUid.src[0].end, 150395, "result mismatched."); + Assert.assertEquals(copyUid.dst.length, 1, "result mismatched."); + Assert.assertEquals(copyUid.dst[0].start, 3, "result mismatched."); + Assert.assertEquals(copyUid.dst[0].end, 3, "result mismatched."); + } + + /** + * Tests ImapResponseParse parseCopyUid when Responses array is empty. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseCopyUidResponseArrayZero() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, CopyUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseAppendUid successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseAppendUidSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[3]; + content[0] = new IMAPResponse("+ Ready for literal data"); + content[1] = new IMAPResponse("* 3 EXISTS"); + content[2] = new IMAPResponse("a5 OK [APPENDUID 1459808247 150399] APPEND completed"); + final AppendUID appendUid = mapper.readValue(content, AppendUID.class); + + // verify the result + Assert.assertNotNull(appendUid, "result mismatched."); + Assert.assertEquals(appendUid.uidvalidity, 1459808247, "result mismatched."); + Assert.assertEquals(appendUid.uid, 150399, "result mismatched."); + } + + /** + * Tests parseToCapabilities method when ImapResponse array has zero length. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToAppendUidsArrayLengthZero() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = {}; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseAppendUid with a BAD response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseAppendUidNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("* BAD Some junks") }; + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseAppendUid when b is 0, or when left bracket is not found. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseAppendUidByteReadExhausted() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("* OK") }; // test when b is 0 + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseAppendUid when b is 0. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseAppendUidNoAppendUidKeyword() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("* OK [appendButNotUid 111 222 3]") }; // test when b is 0 + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, AppendUID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseMailboxInfo method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[8]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); + final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + } + + /** + * Tests parseExtensionMailboxInfo method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseExtensionMailboxInfoReadOnlySuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[9]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("* OK [MAILBOXID (A26)] Ok"); + content[8] = new IMAPResponse("002 OK [READ-ONLY] EXAMINE completed; now in selected state"); + final ExtensionMailboxInfo minfo = mapper.readValue(content, ExtensionMailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, Folder.READ_ONLY, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + Assert.assertEquals(minfo.getMailboxId(), "A26", "MailboxId mismatched."); + } + + /** + * Tests parseMailboxInfo method successfully with READ-WRITE mode. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseMailboxInfoReadWriteSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[8]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("002 OK [READ-WRITE] EXAMINE completed; now in selected state"); + final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, Folder.READ_WRITE, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + } + + /** + * Tests parseMailboxInfo method successfully with READ-WRITE mode. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseMailboxInfoBad() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[8]; + content[0] = new IMAPResponse("* 3 EXISTS"); + content[1] = new IMAPResponse("* 0 RECENT"); + content[2] = new IMAPResponse("* OK [UIDVALIDITY 1459808247] UIDs valid"); + content[3] = new IMAPResponse("* OK [UIDNEXT 150400] Predicted next UID"); + content[4] = new IMAPResponse("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded $Junk $NotJunk)"); + content[5] = new IMAPResponse("* OK [PERMANENTFLAGS ()] No permanent flags permitted"); + content[6] = new IMAPResponse("* OK [HIGHESTMODSEQ 614]"); + content[7] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode + final MailboxInfo minfo = mapper.readValue(content, MailboxInfo.class); + + // verify the result + Assert.assertNotNull(minfo, "result mismatched."); + Assert.assertEquals(minfo.mode, 0, "mode mismatched."); + Assert.assertNotNull(minfo.availableFlags, "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.ANSWERED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DELETED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.DRAFT), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.FLAGGED), "availableFlags mismatched."); + Assert.assertTrue(minfo.availableFlags.contains(Flag.SEEN), "availableFlags mismatched."); + Assert.assertEquals(minfo.highestmodseq, 614, "highestmodseq mismatched."); + Assert.assertEquals(minfo.uidvalidity, 1459808247, "uidvalidity mismatched."); + Assert.assertEquals(minfo.uidnext, 150400, "uidnext mismatched."); + } + + /** + * Tests parseMailboxInfo method successfully with READ-WRITE mode. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseMailboxInfoResponseArray0() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, MailboxInfo.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Adds the data for test input. + * + * @param rr output parameter, list of IMAPResponse + * @param expectedNames output parameter, list of expected folder names + * @param respStr response string + * @param folder folder name + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + private void buildListInfoIMAPResponse(final List rr, final List expectedNames, final String respStr, final String folder) + throws IOException, ProtocolException { + expectedNames.add(folder); + rr.add(new IMAPResponse(respStr + " \"" + folder + "\"\r\n")); + } + + /** + * Tests parseListInfos method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListInfosSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + // add a non-relevant response + content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); + buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "Archive"); + content.add(new IMAPResponse("* some junks\r\n")); + buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); + buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "Draft"); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "Inbox"); + buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "Sent"); + buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "Trash"); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "test1"); + content.add(new IMAPResponse("* 115141 EXISTS\r\n")); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "test1/test1_1"); + content.add(new IMAPResponse("a3 OK LIST completed")); + final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + final List infos = ll.getListInfo(); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); + Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); + for (int i = 0; i < infos.size(); i++) { + final ListInfo info = infos.get(i); + final String expectedFolder = names.get(i); + Assert.assertNotNull(info, "ListInfo should not be null."); + Assert.assertNotNull(expectedFolder, "folder name should not be null."); + Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); + Assert.assertNotNull(info.name, "Name mismatched."); + Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); + } + } + + /** + * Tests parseListInfos method successfully when responses are results of LSUB command. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListInfosFromLSubSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Archive \\HasNoChildren) \"/\"", "Archive"); + content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); + content.add(new IMAPResponse("* MORE JUNKS\r\n")); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Junk \\HasNoChildren) \"/\"", "Bulk Mail"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Drafts \\HasNoChildren) \"/\"", "Draft"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "Inbox"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Sent \\HasNoChildren) \"/\"", "Sent"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\Trash \\HasNoChildren) \"/\"", "Trash"); + buildListInfoIMAPResponse(content, names, "* LSUB (\\HasChildren) \"/\"", "test1"); + content.add(new IMAPResponse("* 115141 EXISTS\r\n")); + buildListInfoIMAPResponse(content, names, "* LSUB (\\HasNoChildren) \"/\"", "test1/test1_1"); + content.add(new IMAPResponse("a3 OK LSUB completed")); + final ListInfoList ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + final List infos = ll.getListInfo(); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + Assert.assertEquals(infos.size(), 8, "ListInfo count mismatched."); + Assert.assertEquals(infos.size(), names.size(), "ListInfo count mismatched."); + for (int i = 0; i < infos.size(); i++) { + final ListInfo info = infos.get(i); + final String expectedFolder = names.get(i); + Assert.assertNotNull(info, "ListInfo should not be null."); + Assert.assertNotNull(expectedFolder, "folder name should not be null."); + Assert.assertTrue(info.hasInferiors, "hasInferiors mismatched."); + Assert.assertNotNull(info.name, "Name mismatched."); + Assert.assertEquals(info.name, expectedFolder, "folder name mismatched."); + } + } + + /** + * Tests parseListInfos method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseListInfosNoOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); + content.add(new IMAPResponse("a3 BAD LIST completed")); + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseListInfos method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListInfosOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List rr = new ArrayList<>(); + rr.add(new IMAPResponse("a3 OK LIST completed")); + final ListInfoList infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListInfoList.class); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + } + + /** + * Tests parseListInfos method when response array length is 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseListInfosEmptyResponses() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListInfoList.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseListStatus method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListStatusSuccess() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List expectedLinfo = new ArrayList<>(); + final List expectedStatuses = new ArrayList<>(); + + // 0 + final String lresp0 = "* LIST (\\HasNoChildren) \"/\" \"INBOX\""; + content.add(new IMAPResponse(lresp0)); + // add some non-relevant responses + content.add(new IMAPResponse("* 115140 EXPUNGE\r\n")); + content.add(new IMAPResponse("* 115141 EXPUNGE\r\n")); + final String sresp0 = "* STATUS \"INBOX\" (HIGHESTMODSEQ 82676 MESSAGES 774 UIDNEXT 913 UIDVALIDITY 1 UNSEEN 769)"; + popluateContentAndBuildStatus(lresp0, sresp0, content, expectedLinfo, expectedStatuses); + + // 1 + content.add(new IMAPResponse("* 115142 EXPUNGE\r\n")); + final String lresp1 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"[Zmail]\""; + content.add(new IMAPResponse(lresp1)); + content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); + popluateContentAndBuildStatus(lresp1, null, content, expectedLinfo, expectedStatuses); // status is null + + // 2 + final String lresp2 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/All Mail\""; + content.add(new IMAPResponse(lresp2)); + final String sresp2 = "* STATUS \"[Zmail]/All Mail\" (HIGHESTMODSEQ 82676 MESSAGES 777 UIDNEXT 1109 UIDVALIDITY 12 UNSEEN 770)"; + popluateContentAndBuildStatus(lresp2, sresp2, content, expectedLinfo, expectedStatuses); + + // 3 + final String lresp3 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Drafts\""; + content.add(new IMAPResponse(lresp3)); + final String sresp3 = "* STATUS \"[Zmail]/Drafts\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 98 UIDVALIDITY 6 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp3, sresp3, content, expectedLinfo, expectedStatuses); + + // 4 + final String lresp4 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Important\""; + content.add(new IMAPResponse(lresp4)); + final String sresp4 = "* STATUS \"[Zmail]/Important\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 118 UIDVALIDITY 9 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp4, sresp4, content, expectedLinfo, expectedStatuses); + + // 5 + final String lresp5 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Sent Mail\""; + content.add(new IMAPResponse(lresp5)); + final String sresp5 = "* STATUS \"[Zmail]/Sent Mail\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 88 UIDVALIDITY 5 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp5, sresp5, content, expectedLinfo, expectedStatuses); + + // 6 + final String lresp6 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Spam\""; + content.add(new IMAPResponse(lresp6)); + final String sresp6 = "* STATUS \"[Zmail]/Spam\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 101 UIDVALIDITY 3 UNSEEN 1)"; + popluateContentAndBuildStatus(lresp6, sresp6, content, expectedLinfo, expectedStatuses); + + // 7 + final String lresp7 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Starred\""; + content.add(new IMAPResponse(lresp7)); + final String sresp7 = "* STATUS \"[Zmail]/Starred\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 9 UIDVALIDITY 4 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp7, sresp7, content, expectedLinfo, expectedStatuses); + + // 8 + final String lresp8 = "* LIST (\\HasNoChildren) \"/\" \"[Zmail]/Trash\""; + content.add(new IMAPResponse(lresp8)); + final String sresp8 = "* STATUS \"[Zmail]/Trash\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 137 UIDVALIDITY 2 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp8, sresp8, content, expectedLinfo, expectedStatuses); + + // 9 + final String lresp9 = "* LIST (\\HasChildren) \"/\" \"parent_folder\""; + content.add(new IMAPResponse(lresp9)); + final String sresp9 = "* STATUS \"parent_folder\" (HIGHESTMODSEQ 82676 MESSAGES 0 UIDNEXT 1 UIDVALIDITY 15 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp9, sresp9, content, expectedLinfo, expectedStatuses); + + // 10 + final String lresp10 = "* LIST (\\HasNoChildren) \"/\" \"parent_folder/child_folder\""; + content.add(new IMAPResponse(lresp10)); + final String sresp10 = "* STATUS \"parent_folder/child_folder\" (HIGHESTMODSEQ 82676 MESSAGES 1 UIDNEXT 2 UIDVALIDITY 16 UNSEEN 0)"; + popluateContentAndBuildStatus(lresp10, sresp10, content, expectedLinfo, expectedStatuses); + + // 11 + final String lresp11 = "* LIST (\\HasChildren \\NonExistent) \"/\" \"abc_folder\""; + content.add(new IMAPResponse(lresp11)); + content.add(new IMAPResponse("* 115143 EXPUNGE\r\n")); + popluateContentAndBuildStatus(lresp11, null, content, expectedLinfo, expectedStatuses); // status is null + + content.add(new IMAPResponse("a3 OK Success")); + + final ListStatusResult ll = mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); + + // verify the result + final List infos = ll.getListInfos(); + Assert.assertNotNull(infos, "result mismatched."); + Assert.assertEquals(infos.size(), 12, "ListInfo count mismatched."); + final Map statuses = ll.getStatuses(); + + for (int i = 0; i < infos.size(); i++) { + final ExtensionListInfo info = infos.get(i); + final ExtensionListInfo expectedInfo = expectedLinfo.get(i); + final Status expectedStatus = expectedStatuses.get(i); + + Assert.assertNotNull(info, "ListStatus should not be null."); + + // verify ListInfo + Assert.assertNotNull(info, "ListInfo should not be null."); + Assert.assertEquals(info.getAvailableExtendedAttributes(), expectedInfo.getAvailableExtendedAttributes(), "Data mismatched."); + + Assert.assertEquals(info.hasInferiors, expectedInfo.hasInferiors, "hasInferiors mismatched."); + Assert.assertEquals(info.name, expectedInfo.name, "ListInfo name mismatched."); + Assert.assertEquals(info.attrs.length, expectedInfo.attrs.length, "ListInfo attrs size mismatched."); + for (int j = 0; j < expectedInfo.attrs.length; j++) { + Assert.assertEquals(info.attrs[j], expectedInfo.attrs[j], "info.attrs[j] mismatched."); + } + final Status st = statuses.get(info.name); + + // Verify Status + if (expectedStatus == null) { // if expecting no status for this folder + Assert.assertNull(st, "Expected Status mismatched."); + continue; + } + Assert.assertEquals(st.mbox, expectedStatus.mbox, "Status.mbox mismatched."); + Assert.assertEquals(info.name, st.mbox, "ListInfo and Status mismatched. "); + Assert.assertEquals(st.highestmodseq, expectedStatus.highestmodseq, "highestmodseq mismatched."); + Assert.assertEquals(st.total, expectedStatus.total, "total mismatched."); + Assert.assertEquals(st.recent, expectedStatus.recent, "recent mismatched."); + Assert.assertEquals(st.uidnext, expectedStatus.uidnext, "highestmodseq mismatched."); + Assert.assertEquals(st.uidvalidity, expectedStatus.uidvalidity, "highestmodseq mismatched."); + Assert.assertEquals(st.unseen, expectedStatus.unseen, "highestmodseq mismatched."); + Assert.assertEquals(st.items, expectedStatus.items, "items mismatched."); + if (expectedStatus.items != null) { + Assert.assertEquals(st.items.size(), expectedStatus.items.size(), "items mismatched."); + for (final String itemName : expectedStatus.items.keySet()) { + Assert.assertEquals(st.items.get(itemName), expectedStatus.items.get(itemName), "Item value mismatched."); + } + } + } + } + + /** + * Builds the Status for expected Status and populate given Content for input. + * + * @param listResp List response in string format + * @param statusResp Status response in string format + * @param content IMAPResponse list + * @param expectedInfo expected ListInfo list + * @param expectedSt expected Status list + * @return + * @throws ProtocolException will not throw + * @throws IOException will not throw + */ + private void popluateContentAndBuildStatus(final String listResp, final String statusResp, final List content, + final List expectedInfo, final List expectedSt) throws IOException, ProtocolException { + + expectedInfo.add(new ExtensionListInfo(new IMAPResponse(listResp))); + if (statusResp != null) { + content.add(new IMAPResponse(statusResp)); + expectedSt.add(new Status(new IMAPResponse(statusResp))); + } else { + expectedSt.add(null); + } + } + + /** + * Tests parseListInfos method when response array length is 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseListStausEmptyResponses() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseListInfos method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseListStatusNoOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + final List names = new ArrayList<>(); + buildListInfoIMAPResponse(content, names, "* LIST (\\Archive \\HasNoChildren) \"/\"", "\"Archive\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Junk \\HasNoChildren) \"/\"", "\"Bulk Mail\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Drafts \\HasNoChildren) \"/\"", "\"Draft\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"Inbox\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Sent \\HasNoChildren) \"/\"", "\"Sent\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\Trash \\HasNoChildren) \"/\"", "\"Trash\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasChildren) \"/\"", "\"test1\""); + buildListInfoIMAPResponse(content, names, "* LIST (\\HasNoChildren) \"/\"", "\"test1/test1_1\""); + content.add(new IMAPResponse("a3 BAD LIST completed")); + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ListStatusResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + + } + + /** + * Tests parseListStatus method when final response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseListStatusOnlyOKResponse() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List rr = new ArrayList<>(); + rr.add(new IMAPResponse("a3 OK LIST completed")); + final ListStatusResult infos = mapper.readValue(rr.toArray(new IMAPResponse[0]), ListStatusResult.class); + + // verify the result + Assert.assertNotNull(infos, "result mismatched."); + } + + /** + * Tests ExtensionMailboxInfo method when response array length is 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseMailboxExtensionInfosEmptyResponses() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), ExtensionMailboxInfo.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStatus method with response array 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseStatusArray0() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Status.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parse a class that mapper does not support. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseClassUnknown() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, ID.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); + } + + /** + * Tests parseStatus method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStatusNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Status.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStatus method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStatusNoStatusResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* NOSTATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)"); + content[1] = new IMAPResponse("A042 OK STATUS completed"); + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, Status.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStatus method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStatusOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292 UNSEEN 3)")); + content.add(new IMAPResponse("* S2TATUS blurdybloop (MESSAGES 232 UIDNEXT 44293)")); + content.add(new IMAPResponse("* STATUS blurdybloop (UNSEEN 4)")); + content.add(new IMAPResponse("* STATUS blurdybloop (UIDVALIDITY 999333)")); + content.add(new IMAPResponse("* STATUS blurdybloop (HIGHESTMODSEQ 2483)")); + content.add(new IMAPResponse("A042 OK STATUS completed")); + + final Status status = mapper.readValue(content.toArray(new IMAPResponse[0]), Status.class); + + // verify the result + Assert.assertNotNull(status, "status mismatched."); + Assert.assertEquals(status.uidnext, 44292, "uidnext mismatched."); + Assert.assertEquals(status.total, 231, "total mismatched."); + Assert.assertEquals(status.unseen, 4, "unseen mismatched, should take the latter one."); + Assert.assertEquals(status.uidvalidity, 999333, "uidvalidity mismatched."); + Assert.assertEquals(status.highestmodseq, 2483, "highest modseq mismatched."); + } + + /** + * Tests parseToID method with response array 0. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultArray0() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToID method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 BAD"); // make it bad so it does not update mode + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToIdResult with first byte is N. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultFirstByteIsN() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID NIL \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + final IdResult id = mapper.readValue(content, IdResult.class); + + // verify the result + Assert.assertNotNull(id, "id mismatched."); + Assert.assertFalse(id.hasKey("name"), "name key mismatched."); + Assert.assertNull(id.getValue("name"), "name value mismatched."); + } + + /** + * Tests parseToIdResult with first byte is n. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultFirstByteIsLowercaseN() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID nIL \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + final IdResult id = mapper.readValue(content, IdResult.class); + + // verify the result + Assert.assertNotNull(id, "id mismatched."); + Assert.assertFalse(id.hasKey("name"), "name key mismatched."); + Assert.assertNull(id.getValue("name"), "name value mismatched."); + } + + /** + * Tests parseToIdResult with first byte not left parenthesis. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultNotStartWithLeftParenthesis() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID X \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToIdResult with first byte not left parenthesis. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultNameAbsent() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + // sun java mail library 1.5.5 has bug fix in readStringList() to return 0 length array instead of 1 length with a null element + + content[0] = new IMAPResponse("* ID () \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + // verify the result + final IdResult id = mapper.readValue(content, IdResult.class); + // verify the result + Assert.assertNotNull(id, "result mismatched."); + } + + /** + * Tests parseToIdResult with first byte not left parenthesis. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToIdResultValueAbsent() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* ID (') \n"); + content[1] = new IMAPResponse("a042 OK ID command completed"); + + // verify the result + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, IdResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToID method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToIdResultOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[3]; + content[0] = new IMAPResponse("* ID (\"name\" \"Cyrus\" \"version\" \"1.5\" \"os\" \"sunos\"\n\"os-version\" \"5.5\" \"support-url\"\n" + + "\"mailto:cyrus-bugs+@andrew.cmu.edu\")\n"); + content[1] = new IMAPResponse("junk"); + content[2] = new IMAPResponse("a042 OK ID command completed"); + + final IdResult id = mapper.readValue(content, IdResult.class); + + // verify the result + Assert.assertNotNull(id, "id mismatched."); + Assert.assertTrue(id.hasKey("name"), "name should be present."); + Assert.assertEquals(id.getValue("name"), "Cyrus", "name value mismatched."); + } + + /** + * Tests parseSearchResult method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNull(result.getHighestModSeq(), "getHighestModSeq() mismatch."); + } + + /** + * Tests parseSearchResult method successfully with modification sequence. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSEQ 2473)\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + final Long modSeq = result.getHighestModSeq(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNotNull(modSeq, "getHighestModSeq() should not return null."); + Assert.assertEquals(modSeq, Long.valueOf(2473), "getHighestModSeq() mismatched."); + } + + /** + * Tests parseSearchResult method successfully without correct modification sequence string. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultNoModSeqOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH 150404 150406 150407 (MODSE 2473)\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + final Long modSeq = result.getHighestModSeq(); + Assert.assertNotNull(list, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.size(), 3, "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(0), Long.valueOf(150404), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(1), Long.valueOf(150406), "getMessageNumbers() mismatched."); + Assert.assertEquals(list.get(2), Long.valueOf(150407), "getMessageNumbers() mismatched."); + Assert.assertNull(modSeq, "getHighestModSeq() should return null."); + } + + /** + * Tests parseSearchResult method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToSearchResultOKNoSearchResult() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* SEARCH\r\n"); + content[1] = new IMAPResponse("a3 OK UID SEARCH completed\r\n"); + + final SearchResult result = mapper.readValue(content, SearchResult.class); + + // verify the result + Assert.assertNotNull(result, "result mismatched."); + final List list = result.getMessageNumbers(); + Assert.assertNotNull(list, "getMessageSequence() mismatched."); + Assert.assertEquals(list.size(), 0, "getMessageSequence() mismatched."); + } + + /** + * Tests parseToSearchResult method when tagged response is not OK. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseToSearchResultZeroLengthResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + + ImapAsyncClientException actual = null; + try { + final SearchResult result = mapper.readValue(content, SearchResult.class); + } catch (final ImapAsyncClientException e) { + actual = e; + } + // verify the result + Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); + Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToSearchResult method when tagged response is not OK. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseToSearchResultNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("a3 BAD SEARCH completed (Failure)\r\n"); + + ImapAsyncClientException actual = null; + try { + final SearchResult result = mapper.readValue(content, SearchResult.class); + } catch (final ImapAsyncClientException e) { + actual = e; + } + // verify the result + Assert.assertNotNull(actual, "ImapAsyncClientException should occur."); + Assert.assertEquals(actual.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToEnableResult method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResult() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 2, "capability missed."); + Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); + Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); + } + + /** + * Tests parseToEnableResult method with response contains length zero line. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultLengthZero() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); + content.add(new IMAPResponse("")); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 2, "capability missed."); + Assert.assertTrue(capas.contains("CONDSTORE"), "One capability missed."); + Assert.assertTrue(capas.contains("QRSYNC"), "One capability missed."); + } + + /** + * Tests parseToEnableResult method with response contains only * ENABLED but no capability. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNoCapa() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* ENABLED\r\n")); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 0, "capability missed."); + } + + /** + * Tests parseToEnableResult method when server does not enable all the capability. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNotEnabled() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("a3 OK ENABLE completed\r\n")); + final EnableResult enableResult = mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + + // verify the result + Assert.assertNotNull(enableResult, "result should never return null."); + final Set capas = enableResult.getEnabledCapabilities(); + Assert.assertEquals(capas.size(), 0, "capability missed."); + } + + /** + * Tests parseToEnableResult method when server returns with no response contents. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNoContent() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseToEnableResult method when server returns with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseToEnableResultNotOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final List content = new ArrayList<>(); + content.add(new IMAPResponse("* some junks\r\n")); + content.add(new IMAPResponse("* ENABLED CONDSTORE QRSYNC\r\n")); + content.add(new IMAPResponse("a3 BAD ENABLE fail\r\n")); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content.toArray(new IMAPResponse[0]), EnableResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + /** + * Tests parseStore method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("A042 OK Store success"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + } + + /** + * Tests parseStore method successfully with CondStore. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with CondStore and wrong modified string. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreWithWrongModified() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIE 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should return null."); + } + + /** + * Tests parseStore method successfully with No response and CondStore. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHEST 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 No [MODIFIED 1] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + Assert.assertNull(storeResult.getHighestModSeq(), "getHighestModSeq() should return null."); + final List irs = storeResult.getFetchResponses(); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(0) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(1) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertTrue(irs.get(2) instanceof FetchResponse, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null."); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() size mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with multiple modification numbers. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreWithMultipleModified() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[3] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1:2,5:4] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result should not be null."); + final List irs = storeResult.getFetchResponses(); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() mismatched"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 2, "getModifiedMsgsets() mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1:2,4:5", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with vanished response.. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreOKCondStoreVanished() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[5]; + content[0] = new IMAPResponse("* OK [HIGHESTMODSEQ 2682"); + content[1] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2600))"); + content[3] = new IMAPResponse("* VANISHED (EARLIER) 41,43:116,118,120:211,214:540"); + content[4] = new IMAPResponse("a4 OK [MODIFIED 1,2,3] Conditional Store Failed (Success)"); + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + final List irs = storeResult.getFetchResponses(); + Assert.assertNotNull(storeResult.getHighestModSeq(), "getHighestModSeq() should not return null."); + Assert.assertEquals(storeResult.getHighestModSeq(), Long.valueOf(2682), "getHighestModSeq() mismatched."); + Assert.assertEquals(irs.size(), 2, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 3, "getModifiedMsgsets() mismatched"); + final String msgSets = MessageNumberSet.buildString(storeResult.getModifiedMsgSets()); + Assert.assertEquals(msgSets, "1,2,3", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method with Bad response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreBad() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with No response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreNo() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with 0 response throw exception. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreZeroResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method with invalid fetch response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseStoreFailParsingFetchResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* FETCH abc (FLAG (\\Seen SEEN))"); + content[1] = new IMAPResponse("* OK"); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, StoreResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseStore method successfully with NO response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoResponseCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("B001 NO [MODIFIED 2] Some of the messages no longer exist.") }; + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); + Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseStore method successfully with NO response and space after left bracket. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseStoreNoResponseCondStoreWithSpace() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("B001 NO [ MODIFIED 2] Some of the messages no longer exist.") }; + final StoreResult storeResult = mapper.readValue(content, StoreResult.class); + + // verify the result + Assert.assertNotNull(storeResult, "store result mismatched."); + Assert.assertEquals(storeResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + Assert.assertNotNull(storeResult.getModifiedMsgSets(), "getModifiedMsgsets() should not return null"); + Assert.assertEquals(storeResult.getModifiedMsgSets().length, 1, "getModifiedMsgsets() mismatched."); + Assert.assertEquals(MessageNumberSet.buildString(storeResult.getModifiedMsgSets()), "2", "getModifiedMsgsets() mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOK() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("A042 OK Fetch success") }; + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + + // verify the result + Assert.assertNotNull(fetchResult, "Fetch result mismatched."); + Assert.assertEquals(fetchResult.getFetchResponses().size(), 0, "getFetchResponses() mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOKCondStore() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[4]; + content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[2] = new IMAPResponse("* 3 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[3] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "Fetch result mismatched."); + Assert.assertEquals(irs.size(), 3, "getFetchResponses() mismatched."); + } + + /** + * Tests parseFetch method with not OK response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchNotOK() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = { new IMAPResponse("002 BAD") }; // make it bad so it does not update mode + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method with 0 response. + * + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchZeroResponse() throws ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[0]; + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method with invalid fetch response. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseFetchFailParsingFetchResponse() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* FETCH abc (FLAG (\\Seen SEEN))"); + content[1] = new IMAPResponse("* OK"); + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, FetchResult.class); + } catch (final ImapAsyncClientException e) { + cause = e; + } + + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.INVALID_INPUT, "Failure type mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchOKCondStoreExpunge() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[6]; + content[0] = new IMAPResponse("* 1 EXPUNGE"); + content[1] = new IMAPResponse("* 2 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[2] = new IMAPResponse("* 3 EXPUNGE"); + content[3] = new IMAPResponse("* 4 FETCH (FLAGS (\\Seen) MODSEQ (2531))"); + content[4] = new IMAPResponse("* 5 FETCH (FLAGS (\\Seen) MODSEQ (2648))"); + content[5] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "fetch result mismatched."); + Assert.assertEquals(irs.size(), 3, "fetch result mismatched."); + } + + /** + * Tests parseFetch method successfully. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + * @throws ImapAsyncClientException will not throw + */ + @Test + public void testParseFetchExtension() throws IOException, ProtocolException, ImapAsyncClientException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[2]; + content[0] = new IMAPResponse("* 1 FETCH (FLAGS (\\Seen SEEN) MODSEQ (2529))"); + content[1] = new IMAPResponse("a4 OK Success"); + final FetchResult fetchResult = mapper.readValue(content, FetchResult.class, new FetchItem[0]); + final List irs = fetchResult.getFetchResponses(); + + // verify the result + Assert.assertNotNull(fetchResult, "fetch result mismatched."); + Assert.assertEquals(irs.size(), 1, "fetch result mismatched."); + } + + /** + * Tests parse a class that mapper does not support. + * + * @throws IOException will not throw + * @throws ProtocolException will not throw + */ + @Test + public void testParseClassUnknownWithFetchExtension() throws IOException, ProtocolException { + final ImapResponseMapper mapper = new ImapResponseMapper(); + final IMAPResponse[] content = new IMAPResponse[1]; + content[0] = new IMAPResponse("002 OK"); // make it bad so it does not update mode + + ImapAsyncClientException cause = null; + try { + mapper.readValue(content, ID.class, new FetchItem[0]); + } catch (final ImapAsyncClientException e) { + cause = e; + } + // verify the result + Assert.assertNotNull(cause, "cause mismatched."); + Assert.assertEquals(cause.getFailureType(), FailureType.UNKNOWN_PARSE_RESULT_TYPE, "Failure type mismatched."); + } +} diff --git a/pom.xml b/pom.xml index 9cd444b0..27ab3239 100644 --- a/pom.xml +++ b/pom.xml @@ -1,598 +1,598 @@ - - - 4.0.0 - com.yahoo.imapnio - imapnio - pom - 4.3.7 - ${project.artifactId} - https://github.com/yahoo/imapnio - Parent POM file for imapnio project - - 3.0 - - - - core - - - - - Yahoo Inc. - https://github.com/yahoo - - - - - scm:git:https://github.com/yahoo/imapnio.git - scm:git:https://github.com/yahoo/imapnio.git - https://github.com/yahoo/imapnio.git - imapnio-1.0 - - - - GitHub - https://github.com/yahoo/imapnio/issues - - - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - imapnio - UTF-8 - ${project.build.sourceEncoding} - - - 1.8 - 3.0.5 - 2.17 - 3.4 - 2.13 - 2.18 - 2.5.3 - 0.8.5 - 0 - ${project.basedir} - - - - - - - - - - - org.testng - testng - 6.8.8 - test - - - junit - junit - - - - - - - com.google.code.findbugs - jsr305 - 1.3.9 - - - io.netty - netty-handler - 4.1.48.Final - - - com.sun.mail - javax.mail - 1.5.5 - - - org.slf4j - slf4j-api - 1.7.12 - - - ch.qos.logback - logback-core - 1.2.0 - - - ch.qos.logback - logback-classic - 1.2.0 - - - commons-codec - commons-codec - 1.10 - - - org.mockito - mockito-all - 1.10.19 - test - - - - - - - ${project.artifactId} - - - org.apache.maven.wagon - wagon-ssh-external - 1.0-beta-6 - - - - - - org.jacoco - jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - - org.codehaus.mojo - build-helper-maven-plugin - 1.9.1 - - - org.apache.maven.plugins - maven-release-plugin - 2.5.2 - - - maven-assembly-plugin - 2.5.5 - - - maven-clean-plugin - 2.6.1 - - - maven-enforcer-plugin - 1.4 - - - maven-pmd-plugin - ${maven-pmd-plugin.version} - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - maven-project-info-reports-plugin - 2.8 - - - maven-site-plugin - 3.4 - - - maven-source-plugin - 3.2.0 - - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - - org.codehaus.mojo - findbugs-maven-plugin - ${maven-findbugs-plugin.version} - - - org.apache.maven.plugins - maven-scm-plugin - 1.9.4 - false - - - org.apache.maven.plugins - maven-surefire-report-plugin - ${maven-surefire-plugin.version} - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.codehaus.mojo - findbugs-maven-plugin - [3.0.1,) - - check - - - - - - - - - org.codehaus.mojo - javacc-maven-plugin - [2.6,) - - javacc - - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - [2.9,) - - copy-dependencies - - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - [1.3.1,) - - enforce - - - - - - - - - - org.apache.maven.plugins - - - maven-checkstyle-plugin - - - [2.13,) - - - check - - - - - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - 128m - 512m - ${jdk.version} - ${jdk.version} - true - true - true - UTF-8 - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce - - enforce - - - - - ${jdk.version} - - - - - - - - org.apache.maven.plugins - maven-scm-plugin - false - - connection - *.state,*.yidf - ${main.basedir}/ci/buildtime - Auto-update-build - - - - pre-site - - checkin - - - - - - org.codehaus.mojo - findbugs-maven-plugin - - - true - 2048 - - true - - low - - true - ${main.basedir}/findbugs-include.xml - ${main.basedir}/findbugs-exclude.xml - medium - - false - false - - - - - check-compile - verify - - check - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - false - true - 1 - - ${main.basedir}/pmd-ruleset.xml - - false - true - true - - - - pmd - - check - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - once - - -Dfile.encoding=ANSI_X3.4-1968 - -Djava.library.path= - -javaagent:"${settings.localRepository}"/org/jacoco/org.jacoco.agent/${jacoco-maven-plugin.version}/org.jacoco.agent-${jacoco-maven-plugin.version}-runtime.jar=destfile=${basedir}/target/jacoco.exec - notIsolate,EventListenersRegression - - true - false - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - checkstyle - validate - - UTF-8 - true - false - checkstyle.xml - true - true - true - config_loc=${main.basedir} - checkstyle_suppressions.xml - checkstyle.suppressions.file - - - check - - - - - - org.jacoco - jacoco-maven-plugin - - - jacoco-initialize - initialize - - prepare-agent - - - ${basedir}/target/jacoco.exec - - - - jacoco-site - package - - report - - - ${basedir}/target/jacoco.exec - ${basedir}/target/jacoco - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - 8 - - - - attach-javadocs - - jar - - - - - - - - - - commit - - test - - - - commit - - - - - - - - - ossrh - - gpg - ${env.GPG_KEYNAME} - ${env.GPG_PASSPHRASE} - false - ${main.basedir}/ci/deploy - pubring.gpg - secring.gpg - - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - --pinentry-mode - loopback - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - - - - + + + 4.0.0 + com.yahoo.imapnio + imapnio + pom + 4.3.8 + ${project.artifactId} + https://github.com/yahoo/imapnio + Parent POM file for imapnio project + + 3.0 + + + + core + + + + + Yahoo Inc. + https://github.com/yahoo + + + + + scm:git:https://github.com/yahoo/imapnio.git + scm:git:https://github.com/yahoo/imapnio.git + https://github.com/yahoo/imapnio.git + imapnio-1.0 + + + + GitHub + https://github.com/yahoo/imapnio/issues + + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + imapnio + UTF-8 + ${project.build.sourceEncoding} + + + 1.8 + 3.0.5 + 2.17 + 3.4 + 2.13 + 2.18 + 2.5.3 + 0.8.5 + 0 + ${project.basedir} + + + + + + + + + + + org.testng + testng + 6.8.8 + test + + + junit + junit + + + + + + + com.google.code.findbugs + jsr305 + 1.3.9 + + + io.netty + netty-handler + 4.1.48.Final + + + com.sun.mail + javax.mail + 1.5.5 + + + org.slf4j + slf4j-api + 1.7.12 + + + ch.qos.logback + logback-core + 1.2.0 + + + ch.qos.logback + logback-classic + 1.2.0 + + + commons-codec + commons-codec + 1.10 + + + org.mockito + mockito-all + 1.10.19 + test + + + + + + + ${project.artifactId} + + + org.apache.maven.wagon + wagon-ssh-external + 1.0-beta-6 + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + + org.codehaus.mojo + build-helper-maven-plugin + 1.9.1 + + + org.apache.maven.plugins + maven-release-plugin + 2.5.2 + + + maven-assembly-plugin + 2.5.5 + + + maven-clean-plugin + 2.6.1 + + + maven-enforcer-plugin + 1.4 + + + maven-pmd-plugin + ${maven-pmd-plugin.version} + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + maven-project-info-reports-plugin + 2.8 + + + maven-site-plugin + 3.4 + + + maven-source-plugin + 3.2.0 + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + org.codehaus.mojo + findbugs-maven-plugin + ${maven-findbugs-plugin.version} + + + org.apache.maven.plugins + maven-scm-plugin + 1.9.4 + false + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-plugin.version} + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.mojo + findbugs-maven-plugin + [3.0.1,) + + check + + + + + + + + + org.codehaus.mojo + javacc-maven-plugin + [2.6,) + + javacc + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + [2.9,) + + copy-dependencies + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.3.1,) + + enforce + + + + + + + + + + org.apache.maven.plugins + + + maven-checkstyle-plugin + + + [2.13,) + + + check + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + 128m + 512m + ${jdk.version} + ${jdk.version} + true + true + true + UTF-8 + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce + + enforce + + + + + ${jdk.version} + + + + + + + + org.apache.maven.plugins + maven-scm-plugin + false + + connection + *.state,*.yidf + ${main.basedir}/ci/buildtime + Auto-update-build + + + + pre-site + + checkin + + + + + + org.codehaus.mojo + findbugs-maven-plugin + + + true + 2048 + + true + + low + + true + ${main.basedir}/findbugs-include.xml + ${main.basedir}/findbugs-exclude.xml + medium + + false + false + + + + + check-compile + verify + + check + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + false + true + 1 + + ${main.basedir}/pmd-ruleset.xml + + false + true + true + + + + pmd + + check + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + + -Dfile.encoding=ANSI_X3.4-1968 + -Djava.library.path= + -javaagent:"${settings.localRepository}"/org/jacoco/org.jacoco.agent/${jacoco-maven-plugin.version}/org.jacoco.agent-${jacoco-maven-plugin.version}-runtime.jar=destfile=${basedir}/target/jacoco.exec + notIsolate,EventListenersRegression + + true + false + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + checkstyle + validate + + UTF-8 + true + false + checkstyle.xml + true + true + true + config_loc=${main.basedir} + checkstyle_suppressions.xml + checkstyle.suppressions.file + + + check + + + + + + org.jacoco + jacoco-maven-plugin + + + jacoco-initialize + initialize + + prepare-agent + + + ${basedir}/target/jacoco.exec + + + + jacoco-site + package + + report + + + ${basedir}/target/jacoco.exec + ${basedir}/target/jacoco + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + 8 + + + + attach-javadocs + + jar + + + + + + + + + + commit + + test + + + + commit + + + + + + + + + ossrh + + gpg + ${env.GPG_KEYNAME} + ${env.GPG_PASSPHRASE} + false + ${main.basedir}/ci/deploy + pubring.gpg + secring.gpg + + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + + +