/*
 * Copyright (C) 2005-2014 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package org.alfresco.repo.imap;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Properties;
import javax.mail.Flags;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.search.AndTerm;
import javax.mail.search.BodyTerm;
import javax.mail.search.ComparisonTerm;
import javax.mail.search.FromTerm;
import javax.mail.search.ReceivedDateTerm;
import javax.mail.search.SearchTerm;
import javax.mail.search.SentDateTerm;
import javax.mail.search.SubjectTerm;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ImapModel;
import org.alfresco.repo.imap.AlfrescoImapConst.ImapViewMode;
import org.alfresco.repo.importer.ACPImportPackageHandler;
import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
import org.alfresco.repo.node.integrity.IntegrityChecker;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileFolderUtil;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.view.ImporterService;
import org.alfresco.service.cmr.view.Location;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap;
import org.alfresco.util.config.RepositoryFolderConfigBean;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import com.icegreen.greenmail.store.SimpleStoredMessage;
/**
 * Unit test for ImapServiceImpl
 */
@Category(OwnJVMTestsCategory.class)
public class ImapServiceImplTest extends TestCase 
{
    private static final String IMAP_ROOT = "Alfresco IMAP";
    private static final String APP_COMPANY_HOME = "/app:company_home";
    private static final String USER_NAME = "admin";
    private static final String USER_PASSWORD = "admin";
    private static final String TEST_IMAP_FOLDER_NAME = "aaa";
    private static final String MAILBOX_NAME_A = "mailbox_a";
    private static final String MAILBOX_NAME_B = "mailbox_b";
    private static final String MAILBOX_PATTERN = "mailbox*";
    private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
    private TransactionService transactionService;
    private NodeService nodeService;
    private ImporterService importerService;
    private PersonService personService;
    private MutableAuthenticationService authenticationService;
    private PermissionService permissionService;
    private SearchService searchService;
    private NamespaceService namespaceService;
    private FileFolderService fileFolderService;
    private AlfrescoImapUser user;
    private ImapService imapService;
    private UserTransaction txn;
    private ContentService contentService;
    private NodeRef testImapFolderNodeRef;
    private Flags flags;
    
    String anotherUserName;
    
    @Override
    public void setUp() throws Exception
    {
        ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry");
        transactionService = serviceRegistry.getTransactionService();
        nodeService = serviceRegistry.getNodeService();
        importerService = serviceRegistry.getImporterService();
        personService = serviceRegistry.getPersonService();
        authenticationService = serviceRegistry.getAuthenticationService();
        permissionService = serviceRegistry.getPermissionService();
        imapService = serviceRegistry.getImapService();
        searchService = serviceRegistry.getSearchService();
        namespaceService = serviceRegistry.getNamespaceService();
        fileFolderService = serviceRegistry.getFileFolderService();
        contentService = serviceRegistry.getContentService();
        
        flags = new Flags();
        flags.add(Flags.Flag.SEEN);
        flags.add(Flags.Flag.FLAGGED);
        flags.add(Flags.Flag.ANSWERED);
        flags.add(Flags.Flag.DELETED);
        // start the transaction
        txn = transactionService.getUserTransaction();
        txn.begin();
        authenticationService.authenticate(USER_NAME, USER_PASSWORD.toCharArray());
        // downgrade integrity
        IntegrityChecker.setWarnInTransaction();
        anotherUserName = "user" + System.currentTimeMillis();
        
        PropertyMap testUser = new PropertyMap();
        testUser.put(ContentModel.PROP_USERNAME, anotherUserName);
        testUser.put(ContentModel.PROP_FIRSTNAME, anotherUserName);
        testUser.put(ContentModel.PROP_LASTNAME, anotherUserName);
        testUser.put(ContentModel.PROP_EMAIL, anotherUserName + "@alfresco.com");
        testUser.put(ContentModel.PROP_JOBTITLE, "jobTitle");
        
        personService.createPerson(testUser);
        
        // create the ACEGI Authentication instance for the new user
        authenticationService.createAuthentication(anotherUserName, anotherUserName.toCharArray());
        
        user = new AlfrescoImapUser(anotherUserName + "@alfresco.com", anotherUserName, anotherUserName);
        NodeRef companyHomeNodeRef = findCompanyHomeNodeRef();
        ChildApplicationContextFactory imap = (ChildApplicationContextFactory) ctx.getBean("imap");
        ApplicationContext imapCtx = imap.getApplicationContext();
        ImapServiceImpl imapServiceImpl = (ImapServiceImpl)imapCtx.getBean("imapService");
        // Creating IMAP test folder for IMAP root
        LinkedList folders = new LinkedList();
        folders.add(TEST_IMAP_FOLDER_NAME);
        FileFolderUtil.makeFolders(fileFolderService, companyHomeNodeRef, folders, ContentModel.TYPE_FOLDER);
        
        // Setting IMAP root
        RepositoryFolderConfigBean imapHome = new RepositoryFolderConfigBean();
        imapHome.setStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.toString());
        imapHome.setRootPath(APP_COMPANY_HOME);
        imapHome.setFolderPath(NamespaceService.CONTENT_MODEL_PREFIX + ":" + TEST_IMAP_FOLDER_NAME);
        imapServiceImpl.setImapHome(imapHome);
        
        // Starting IMAP
        imapServiceImpl.startupInTxn(true);
        NodeRef storeRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
        List nodeRefs = searchService.selectNodes(storeRootNodeRef ,
                APP_COMPANY_HOME + "/" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + TEST_IMAP_FOLDER_NAME,
                null,
                namespaceService,
                false);
        testImapFolderNodeRef = nodeRefs.get(0);
        
        /* 
         * Importing test folders:
         * 
         * Test folder contains: "___-___folder_a"
         * 
         * "___-___folder_a" contains: "___-___folder_a_a",
         *                             "___-___file_a",
         *                             "Message_485.eml" (this is IMAP Message)
         *                           
         * "___-___folder_a_a" contains: "____-____file_a_a"
         * 
         */
        importInternal("imap/imapservice_test_folder_a.acp", testImapFolderNodeRef);
        reauthenticate(anotherUserName, anotherUserName);
    }
    public void tearDown() throws Exception
    {
        try
        {
            txn.rollback();
        }
        catch (Throwable e)
        {
            e.printStackTrace();
        }
    }
    private void importInternal(String acpName, NodeRef space)
            throws IOException
    {
        ClassPathResource acpResource = new ClassPathResource(acpName);
        ACPImportPackageHandler acpHandler = new ACPImportPackageHandler(acpResource.getFile(), null);
        Location importLocation = new Location(space);
        importerService.importView(acpHandler, importLocation, null, null);
    }
    private boolean checkMailbox(AlfrescoImapUser user, String mailboxName)
    {
        try
        {
            imapService.getOrCreateMailbox(user, mailboxName, true, false);
        }
        catch (AlfrescoRuntimeException e)
        {
            return false;
        }
        return true;
    }
    private boolean checkSubscribedMailbox(AlfrescoImapUser user, String mailboxName)
    {
        List aifs = imapService.listMailboxes(user, mailboxName, true);
        boolean present = false;
        for (AlfrescoImapFolder aif : aifs)
        {
            if (aif.getName().equals(mailboxName))
            {
                present = true;
                break;
            }
        }
        return present;
    }
    
    private void reauthenticate(String name, String password)
    {
        authenticationService.invalidateTicket(authenticationService.getCurrentTicket());
        authenticationService.clearCurrentSecurityContext();
        authenticationService.authenticate(name, password.toCharArray());
    }
    public void testGetFolder() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        assertTrue(checkMailbox(user, MAILBOX_NAME_A));
    }
    
    public void testListMailbox() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true);
        List mf = imapService.listMailboxes(user, MAILBOX_PATTERN, false);
        assertEquals(2, mf.size());
        
        boolean foundA = false;
        boolean foundB = false;
        
        for(AlfrescoImapFolder folder : mf)
        {
            if(MAILBOX_NAME_A.equals(folder.getName()))
            {
               foundA = true;
            }
            if(MAILBOX_NAME_B.equals(folder.getName()))
            {
               foundB = true;
            }
        }
        
        assertTrue("folder A found", foundA);
        assertTrue("folder B found", foundB);
        
        mf = imapService.listMailboxes(user, MAILBOX_PATTERN, false);
        assertEquals("can't repeat the listing of folders", 2, mf.size());
        
        mf = imapService.listMailboxes(user, MAILBOX_PATTERN, false);
        assertEquals("can't repeat the listing of folders", 2, mf.size());
        
        /**
         * The new mailboxes should be subscribed?
         */
        List aif = imapService.listMailboxes(user, MAILBOX_PATTERN, true);
        assertEquals("not subscribed to two mailboxes", 2, aif.size());
        
        /**
         * Unsubscribe to one of the mailboxes.
         */
        imapService.unsubscribe(user, MAILBOX_NAME_B);
        List aif2 = imapService.listMailboxes(user, MAILBOX_PATTERN, true);
        assertEquals("not subscribed to one mailbox", 1, aif2.size());
    }
    
    public void testListSubscribedMailbox() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true);
        imapService.subscribe(user, MAILBOX_NAME_A);
        imapService.subscribe(user, MAILBOX_NAME_B);
        List aif = imapService.listMailboxes(user, MAILBOX_PATTERN, true);
        assertEquals(aif.size(), 2);
        
        assertTrue("Can't subscribe mailbox A", checkSubscribedMailbox(user, MAILBOX_NAME_A));
        assertTrue("Can't subscribe mailbox B", checkSubscribedMailbox(user, MAILBOX_NAME_B));
    }
    public void testCreateMailbox() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        assertTrue("Mailbox isn't created", checkMailbox(user, MAILBOX_NAME_A));
    }
    public void testDuplicateMailboxes() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        try
        {
            imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
            fail("Duplicate Mailbox was created");
        }
        catch (AlfrescoRuntimeException e)
        {
            // expected
        }
    }
    public void testRenameMailbox() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B);
        assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A));
        assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B));
    }
    public void testRenameMailboxDuplicate() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true);
        try
        {
            imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B);
            fail("Mailbox was renamed to existing one but shouldn't");
        }
        catch (AlfrescoRuntimeException e)
        {
            // expected
        }
    }
    public void testDeleteMailbox() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true);
        imapService.deleteMailbox(user, MAILBOX_NAME_B);
        assertFalse("Can't delete mailbox", checkMailbox(user, MAILBOX_NAME_B));
    }
//    public void testSearchFoldersInArchive() throws Exception
//    {
//        List fi = imapService.searchFolders(testImapFolderNodeRef, FOLDER_PATTERN, true, ImapViewMode.ARCHIVE);
//        assertNotNull("Can't find folders in Archive Mode", fi);
//        assertEquals("Can't find folders in Archive Mode", fi.size(), 2);
//        
//        fi = imapService.searchFolders(testImapFolderNodeRef, FOLDER_PATTERN, false, ImapViewMode.ARCHIVE);
//        assertNotNull("Can't find folders in Archive Mode", fi);
//        assertEquals("Can't find folders in Archive Mode", fi.size(), 1);
//    }
//
//    public void testSearchFoldersInVirtual() throws Exception
//    {
//        List fi = imapService.searchFolders(testImapFolderNodeRef, FOLDER_PATTERN, true, ImapViewMode.VIRTUAL);
//        assertNotNull("Can't find folders in Virtual Mode", fi);
//        assertEquals("Can't find folders in Virtual Mode", fi.size(), 2);
//
//        fi = imapService.searchFolders(testImapFolderNodeRef, FOLDER_PATTERN, false, ImapViewMode.VIRTUAL);
//        assertNotNull("Can't find folders in Virtual Mode", fi);
//        assertEquals("Can't find folders in Virtual Mode", fi.size(), 1);
//    }
//    
//    public void testSearchFoldersInMixed() throws Exception
//    {
//        List fi = imapService.searchFolders(testImapFolderNodeRef, FOLDER_PATTERN, true, ImapViewMode.MIXED);
//        assertNotNull("Can't find folders in Mixed Mode", fi);
//        assertEquals("Can't find folders in Mixed Mode", fi.size(), 2);
//
//        fi = imapService.searchFolders(testImapFolderNodeRef, FOLDER_PATTERN, false, ImapViewMode.MIXED);
//        assertNotNull("Can't find folders in Mixed Mode", fi);
//        assertEquals("Can't find folders in Mixed Mode", fi.size(), 1);
//    }
//    public void testSearchFiles() throws Exception
//    {
//        List fi = imapService.searchFiles(testImapFolderNodeRef, FILE_PATTERN, true);
//        assertNotNull(fi);
//        assertTrue(fi.size() > 0);
//    }
//
//    public void testSearchMails() throws Exception
//    {
//        List fi = imapService.searchMails(testImapFolderNodeRef, ImapViewMode.MIXED);
//        assertNotNull(fi);
//        assertTrue(fi.size() > 0);
//    }
    public void testSubscribe() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        imapService.subscribe(user, MAILBOX_NAME_A);
        assertTrue("Can't subscribe mailbox", checkSubscribedMailbox(user, MAILBOX_NAME_A));
    }
    public void testUnsubscribe() throws Exception
    {
        imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true);
        imapService.subscribe(user, MAILBOX_NAME_A);
        imapService.unsubscribe(user, MAILBOX_NAME_A);
        // TODO MER 21/05/2010 : line below looks like a bug to me.
        assertFalse("Can't unsubscribe mailbox", checkSubscribedMailbox(user, MAILBOX_NAME_A));
    }
    
    private void setFlags(FileInfo messageFileInfo) throws Exception
    {
        imapService.setFlags(messageFileInfo, flags, true);
        NodeRef messageNodeRef = messageFileInfo.getNodeRef();
        Map props = nodeService.getProperties(messageNodeRef);
        assertTrue("Can't set SEEN flag", props.containsKey(ImapModel.PROP_FLAG_SEEN));
        assertTrue("Can't set FLAGGED flag", props.containsKey(ImapModel.PROP_FLAG_FLAGGED));
        assertTrue("Can't set ANSWERED flag", props.containsKey(ImapModel.PROP_FLAG_ANSWERED));
        assertTrue("Can't set DELETED flag", props.containsKey(ImapModel.PROP_FLAG_DELETED));
    }
    public void testSetFlags() throws Exception
    {
        NavigableMap fis = imapService.getFolderStatus(authenticationService.getCurrentUserName(), testImapFolderNodeRef, ImapViewMode.ARCHIVE).search;
        if (fis != null && fis.size() > 0)
        {
            FileInfo messageFileInfo = fis.firstEntry().getValue();
            try
            {
                setFlags(messageFileInfo);
                fail("Can't set flags");
            }
            catch (Exception e)
            {
                if (e instanceof AccessDeniedException)
                {
                    // expected
                }
                else
                {
                    throw e;
                }
            }
            
            reauthenticate(USER_NAME, USER_PASSWORD);
            
            permissionService.setPermission(testImapFolderNodeRef, anotherUserName, PermissionService.WRITE, true);
            
            reauthenticate(anotherUserName, anotherUserName);
            
            setFlags(messageFileInfo);
        }
    }
    
    public void testSetFlag() throws Exception
    {
        NavigableMap fis = imapService.getFolderStatus(authenticationService.getCurrentUserName(), testImapFolderNodeRef, ImapViewMode.ARCHIVE).search;
        if (fis != null && fis.size() > 0)
        {
            FileInfo messageFileInfo = fis.firstEntry().getValue();
            
            reauthenticate(USER_NAME, USER_PASSWORD);
            
            permissionService.setPermission(testImapFolderNodeRef, anotherUserName, PermissionService.WRITE, true);
            
            reauthenticate(anotherUserName, anotherUserName);
            
            imapService.setFlag(messageFileInfo, Flags.Flag.RECENT, true);
            
            Serializable prop = nodeService.getProperty(messageFileInfo.getNodeRef(), ImapModel.PROP_FLAG_RECENT);
            assertNotNull("Can't set RECENT flag", prop);
        }
    }
    public void testGetFlags() throws Exception
    {
        NavigableMap fis = imapService.getFolderStatus(authenticationService.getCurrentUserName(), testImapFolderNodeRef, ImapViewMode.ARCHIVE).search;
        if (fis != null && fis.size() > 0)
        {
            FileInfo messageFileInfo = fis.firstEntry().getValue();
            
            reauthenticate(USER_NAME, USER_PASSWORD);
            
            permissionService.setPermission(testImapFolderNodeRef, anotherUserName, PermissionService.WRITE, true);
            
            imapService.setFlags(messageFileInfo, flags, true);
            
            reauthenticate(anotherUserName, anotherUserName);
            Flags fl = imapService.getFlags(messageFileInfo);
            assertTrue(fl.contains(flags));
        }
    }
    
    public void testRenameAccentedMailbox() throws Exception
    {
        String MAILBOX_ACCENTED_NAME_A = "Hôtel";
        String MAILBOX_ACCENTED_NAME_B = "HôtelXX";
        
        imapService.getOrCreateMailbox(user, MAILBOX_ACCENTED_NAME_A, false, true);
        imapService.deleteMailbox(user, MAILBOX_ACCENTED_NAME_A);
        
        imapService.getOrCreateMailbox(user, MAILBOX_ACCENTED_NAME_A, false, true);
        imapService.renameMailbox(user, MAILBOX_ACCENTED_NAME_A, MAILBOX_ACCENTED_NAME_B);
        assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_ACCENTED_NAME_A));
        assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_ACCENTED_NAME_B));
        imapService.deleteMailbox(user, MAILBOX_ACCENTED_NAME_B);
    }
    
    public void testContentRecovery() throws Exception
    {
        reauthenticate(USER_NAME, USER_PASSWORD);
        
        // create content
        NodeRef nodeRef = nodeService.createNode(testImapFolderNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "content_recover"), ContentModel.TYPE_CONTENT).getChildRef();
        FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef);
        
        // Outlook sets flags that indicates that a content was seen and deleted
        imapService.setFlag(fileInfo, Flags.Flag.DELETED, true);
        imapService.setFlag(fileInfo, Flags.Flag.SEEN, true);
        
        // delete a content
        fileFolderService.delete(nodeRef);
        
        // get archive node reference
        String storePath = "archive://SpacesStore";
        StoreRef storeRef = new StoreRef(storePath);
        NodeRef archivedNodeRef = new NodeRef(storeRef, nodeRef.getId());
       
        // restore a node and check flags
        Boolean value = false;
        if (nodeService.exists(archivedNodeRef))
        {
            NodeRef restoredNode = nodeService.restoreNode(archivedNodeRef, testImapFolderNodeRef, null, null);
            Map props = nodeService.getProperties(restoredNode);
            if (props.containsKey(ImapModel.PROP_FLAG_DELETED) && props.containsKey(ImapModel.PROP_FLAG_SEEN))
            {
                value = !(Boolean) props.get(ImapModel.PROP_FLAG_DELETED) && !(Boolean) props.get(ImapModel.PROP_FLAG_SEEN);
            }
        }
        
        assertTrue("Can't set DELETED flag to false", value);
    }
    
    /**
     * Test attachment extraction with a TNEF message
     * @throws Exception
     */
    public void testAttachmentExtraction() throws Exception
    {
        AuthenticationUtil.setRunAsUserSystem();
        /**
         * Load a TNEF message
         */
        ClassPathResource fileResource = new ClassPathResource("imap/test-tnef-message.eml");
        assertNotNull("unable to find test resource test-tnef-message.eml", fileResource);
        InputStream is = new FileInputStream(fileResource.getFile());
        MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties()), is);
        NodeRef companyHomeNodeRef = findCompanyHomeNodeRef();
        FileInfo f1 = fileFolderService.create(companyHomeNodeRef, "ImapServiceImplTest", ContentModel.TYPE_FOLDER);
        FileInfo f2 = fileFolderService.create(f1.getNodeRef(), "test-tnef-message.eml", ContentModel.TYPE_CONTENT);
        
        ContentWriter writer = fileFolderService.getWriter(f2.getNodeRef());
        writer.putContent(new FileInputStream(fileResource.getFile()));
        
        imapService.extractAttachments(f2.getNodeRef(), message);
        List targetAssocs = nodeService.getTargetAssocs(f2.getNodeRef(), ImapModel.ASSOC_IMAP_ATTACHMENTS_FOLDER);
        assertTrue("attachment folder is found", targetAssocs.size() == 1);
        NodeRef attachmentFolderRef = targetAssocs.get(0).getTargetRef();
        
        assertNotNull(attachmentFolderRef);
        List files = fileFolderService.listFiles(attachmentFolderRef);
        assertTrue("three files not found", files.size() == 3);
    }
    public void testMailboxRenamingInTheRootForMNT9055() throws Exception
    {
        reauthenticate(USER_NAME, USER_PASSWORD);
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        NodeRef root = findCompanyHomeNodeRef();
        String targetNodeName = "Test-" + System.currentTimeMillis();
        FileInfo targetNode = fileFolderService.create(root, targetNodeName, ContentModel.TYPE_FOLDER);
        String renamedNodeName = "Renamed-" + System.currentTimeMillis();
        String renamedNodePath = IMAP_ROOT + AlfrescoImapConst.HIERARCHY_DELIMITER + renamedNodeName;
        assertMailboxRenaming(poweredUser, root, (IMAP_ROOT + AlfrescoImapConst.HIERARCHY_DELIMITER + targetNodeName), java.util.Collections.singletonList(renamedNodeName),
                renamedNodePath, targetNode);
        AlfrescoImapFolder actualMailbox = imapService.getOrCreateMailbox(poweredUser, renamedNodePath, true, false);
        List parentAssocs = nodeService.getParentAssocs(actualMailbox.getFolderInfo().getNodeRef());
        assertNotNull(parentAssocs);
        assertFalse(parentAssocs.isEmpty());
        assertEquals(1, parentAssocs.size());
        assertEquals(root, parentAssocs.iterator().next().getParentRef());
    }
    public void testMailboxRenamingInTheUserImapHomeForMNT9055() throws Exception
    {
        reauthenticate(USER_NAME, USER_PASSWORD);
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        NodeRef root = findCompanyHomeNodeRef();
        String targetNodeName = "Test-" + System.currentTimeMillis();
        String renamedNodeName = "Renamed-" + System.currentTimeMillis();
        AlfrescoImapFolder mailbox = imapService.getOrCreateMailbox(poweredUser, targetNodeName, false, true);
        assertMailboxNotNull(mailbox);
        List pathBeforeRenaming = fileFolderService.getNameOnlyPath(root, mailbox.getFolderInfo().getNodeRef());
        assertMailboxRenaming(poweredUser, root, targetNodeName, Collections.singletonList(renamedNodeName), renamedNodeName, null);
        mailbox = imapService.getOrCreateMailbox(poweredUser, renamedNodeName, true, false);
        assertMailboxNotNull(mailbox);
        List pathAfterRenaming = fileFolderService.getNameOnlyPath(root, mailbox.getFolderInfo().getNodeRef());
        assertPathHierarchy(pathBeforeRenaming, pathAfterRenaming);
    }
    public void testMailboxRenamingInDeepHierarchyForMNT9055() throws Exception
    {
        reauthenticate(USER_NAME, USER_PASSWORD);
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        NodeRef root = findCompanyHomeNodeRef();
        NodeRef parent = root;
        FileInfo targetNode = null;
        String targetNodeName = IMAP_ROOT;
        StringBuilder fullPath = new StringBuilder();
        List fullPathList = new LinkedList();
        String renamedNodeName = "Renamed-" + System.currentTimeMillis();
        for (int i = 0; i < 10; i++)
        {
            fullPath.append(targetNodeName).append(AlfrescoImapConst.HIERARCHY_DELIMITER);
            targetNodeName = new StringBuilder("Test").append(i).append("-").append(System.currentTimeMillis()).toString();
            if (i < 9)
            {
                fullPathList.add(targetNodeName);
            }
            targetNode = fileFolderService.create(parent, targetNodeName, ContentModel.TYPE_FOLDER);
            assertNotNull(targetNode);
            parent = targetNode.getNodeRef();
            assertNotNull(parent);
        }
        String path = fullPath.toString();
        String targetNodePath = path + targetNodeName;
        String renamedNodePath = path + renamedNodeName;
        List renamedFullPathList = new LinkedList(fullPathList);
        renamedFullPathList.add(renamedNodeName);
        fullPathList.add(targetNodeName);
        assertMailboxRenaming(poweredUser, root, targetNodePath, renamedFullPathList, renamedNodePath, targetNode);
        AlfrescoImapFolder mailbox = imapService.getOrCreateMailbox(poweredUser, renamedNodePath, true, false);
        assertMailboxNotNull(mailbox);
        List pathAfterRenaming = fileFolderService.getNameOnlyPath(root, mailbox.getFolderInfo().getNodeRef());
        assertPathHierarchy(fullPathList, pathAfterRenaming);
        FileInfo renamedNode = fileFolderService.resolveNamePath(root, pathAfterRenaming);
        assertNotNull(renamedNode);
        assertEquals(targetNode.getNodeRef(), renamedNode.getNodeRef());
    }
    public void testMiddleMailboxRenamingInDeepHierarchyForMNT9055() throws Exception
    {
        reauthenticate(USER_NAME, USER_PASSWORD);
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        NodeRef root = findCompanyHomeNodeRef();
        NodeRef parent = root;
        String middleName = null; // Name of the middle folder
        FileInfo targetNode = null; // A leaf folder
        FileInfo middleNode = null; // A middle folder
        String targetNodeName = IMAP_ROOT; // Current folder name
        StringBuilder fullPath = new StringBuilder(); // Path from the middle to the leaf folder
        StringBuilder fullLeftPath = new StringBuilder(); // Path to the middle folder
        List fullLeftPathList = new LinkedList(); // Path elements to the middle folder
        List fullRightPathList = new LinkedList(); // Path from the middle to the leaf folder
        String renamedNodeName = "Renamed-" + System.currentTimeMillis();
        for (int i = 0; i < 10; i++)
        {
            if (i <= 5)
            {
                fullLeftPath.append(targetNodeName).append(AlfrescoImapConst.HIERARCHY_DELIMITER);
            }
            else
            {
                if (i > 6)
                {
                    fullPath.append(targetNodeName).append(AlfrescoImapConst.HIERARCHY_DELIMITER);
                }
            }
            targetNodeName = new StringBuilder("Test").append(i).append("-").append(System.currentTimeMillis()).toString();
            if (i < 5)
            {
                fullLeftPathList.add(targetNodeName);
            }
            else
            {
                if (i > 5)
                {
                    fullRightPathList.add(targetNodeName);
                }
            }
            targetNode = fileFolderService.create(parent, targetNodeName, ContentModel.TYPE_FOLDER);
            assertNotNull(targetNode);
            assertNotNull(targetNode.getNodeRef());
            parent = targetNode.getNodeRef();
            if (5 == i)
            {
                middleName = targetNodeName;
                middleNode = targetNode;
            }
        }
        String path = fullLeftPath.toString();
        String targetNodePath = path + middleName;
        String renamedNodePath = path + renamedNodeName;
        List renamedFullLeftPathList = new LinkedList(fullLeftPathList);
        renamedFullLeftPathList.add(renamedNodeName);
        fullLeftPathList.add(middleName);
        assertMailboxRenaming(poweredUser, root, targetNodePath, renamedFullLeftPathList, renamedNodePath, middleNode);
        AlfrescoImapFolder mailbox = imapService.getOrCreateMailbox(poweredUser, renamedNodePath, true, false);
        assertMailboxNotNull(mailbox);
        List pathAfterRenaming = fileFolderService.getNameOnlyPath(root, mailbox.getFolderInfo().getNodeRef());
        assertPathHierarchy(fullLeftPathList, pathAfterRenaming);
        FileInfo renamedNode = fileFolderService.resolveNamePath(root, pathAfterRenaming);
        assertNotNull(renamedNode);
        assertEquals(middleNode.getNodeRef(), renamedNode.getNodeRef());
        String fullRenamedPath = new StringBuilder(path).append(renamedNodeName).append(AlfrescoImapConst.HIERARCHY_DELIMITER).append(fullPath).append(targetNodeName).toString();
        mailbox = imapService.getOrCreateMailbox(poweredUser, fullRenamedPath, true, false);
        assertMailboxNotNull(mailbox);
        assertEquals(targetNode.getNodeRef(), mailbox.getFolderInfo().getNodeRef());
        pathAfterRenaming = fileFolderService.getNameOnlyPath(middleNode.getNodeRef(), targetNode.getNodeRef());
        assertPathHierarchy(fullRightPathList, pathAfterRenaming);
    }
    public void testMailboxMoving() throws Exception
    {
        reauthenticate(USER_NAME, USER_PASSWORD);
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        NodeRef root = findCompanyHomeNodeRef();
        FileInfo targetNode = null;
        String imapUserHomePath = "";
        StringBuilder fullPath = new StringBuilder();
        List fullPathList = new LinkedList(Arrays.asList(new String[] { TEST_IMAP_FOLDER_NAME, USER_NAME }));
        String targetNodeName = new StringBuilder(IMAP_ROOT).append(AlfrescoImapConst.HIERARCHY_DELIMITER).append(TEST_IMAP_FOLDER_NAME).append(
                AlfrescoImapConst.HIERARCHY_DELIMITER).append(USER_NAME).toString();
        for (int i = 0; i < 4; i++)
        {
            fullPath.append(targetNodeName).append(AlfrescoImapConst.HIERARCHY_DELIMITER);
            targetNodeName = new StringBuilder("Test").append(i).append("-").append(System.currentTimeMillis()).toString();
            if (i < 3)
            {
                fullPathList.add(targetNodeName);
            }
            AlfrescoImapFolder mailbox = imapService.getOrCreateMailbox(poweredUser, (imapUserHomePath + targetNodeName), false, true);
            assertNotNull(mailbox);
            targetNode = mailbox.getFolderInfo();
            imapUserHomePath = fullPath.toString() + targetNodeName + AlfrescoImapConst.HIERARCHY_DELIMITER;
        }
        String path = fullPath.toString();
        String targetNodePath = path + targetNodeName;
        String newName = "Renamed-" + System.currentTimeMillis();
        String renamedNodeName = IMAP_ROOT + AlfrescoImapConst.HIERARCHY_DELIMITER + newName;
        imapService.renameMailbox(poweredUser, targetNodePath, renamedNodeName);
        AlfrescoImapFolder mailbox = imapService.getOrCreateMailbox(poweredUser, renamedNodeName, true, false);
        assertMailboxNotNull(mailbox);
        targetNodePath = renamedNodeName;
        renamedNodeName = path + newName;
        imapService.renameMailbox(poweredUser, targetNodePath, renamedNodeName);
        mailbox = imapService.getOrCreateMailbox(poweredUser, renamedNodeName, true, false);
        assertMailboxNotNull(mailbox);
        assertMailboxInUserImapHomeDirectory(root, mailbox.getFolderInfo().getNodeRef());
        List pathAfterRenaming = fileFolderService.getNameOnlyPath(root, targetNode.getNodeRef());
        fullPathList.add(newName);
        assertPathHierarchy(fullPathList, pathAfterRenaming);
    }
    /**
     * Test for MNT-12259
     * There is a 5s gap to run the test, see {@link ImapServiceImpl#hideAndDelete}
     * 
     * @throws Exception 
     */
    public void testMoveViaDeleteAndAppend() throws Exception
    {
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        String fileName = "testfile" + GUID.generate();
        String destinationName = "testFolder" + GUID.generate();
        String destinationPath = IMAP_ROOT + AlfrescoImapConst.HIERARCHY_DELIMITER + destinationName;
        String nodeContent = "test content";
        NodeRef root = findCompanyHomeNodeRef();
        AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
        // Create node and destination folder
        FileInfo origFile = fileFolderService.create(root, fileName, ContentModel.TYPE_CONTENT);
        ContentWriter contentWriter = contentService.getWriter(origFile.getNodeRef(), ContentModel.PROP_CONTENT, true);
        contentWriter.setMimetype("text/plain");
        contentWriter.setEncoding("UTF-8");
        contentWriter.putContent(nodeContent);
        FileInfo destinationNode = fileFolderService.create(root, destinationName, ContentModel.TYPE_FOLDER);
        nodeService.addAspect(origFile.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT, null);
        // Save the message and ensure the message id is set
        SimpleStoredMessage origMessage = imapService.getMessage(origFile);
        origMessage.getMimeMessage().saveChanges();
        imapService.setFlag(origFile, Flags.Flag.DELETED, true);
        // Delete the node
        imapService.expungeMessage(origFile);
        // Append the message to destination
        AlfrescoImapFolder destinationMailbox = imapService.getOrCreateMailbox(poweredUser, destinationPath, true, false);
        destinationMailbox.appendMessage(origMessage.getMimeMessage(), flags, null);
        // original message should be deleted or about to
        assertTrue(!nodeService.exists(origFile.getNodeRef()) || imapService.getFlags(origFile).contains(Flags.Flag.DELETED));
        
        // new file should be in destination
        assertEquals("There should be only one node in the destination folder", 1, nodeService.getChildAssocs(destinationNode.getNodeRef()).size());
        NodeRef newNodeRef = nodeService.getChildAssocs(destinationNode.getNodeRef()).get(0).getChildRef();
        FileInfo newNodeFileInfo = fileFolderService.getFileInfo(newNodeRef);
        assertEquals("The file name should not change.", fileName, newNodeFileInfo.getName());
        ContentReader reader = contentService.getReader(newNodeRef, ContentModel.PROP_CONTENT);
        String contentString = reader.getContentString();
        // new file content should be the same as original one
        assertEquals(contentString, nodeContent);
    }
    
    /**
     * Test for MNT-12420
     * 
     * @throws Exception 
     */
    public void testMoveViaAppendAndDelete() throws Exception
    {
        AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
        String fileName = "testfile" + GUID.generate();
        String destinationName = "testFolder" + GUID.generate();
        String destinationPath = IMAP_ROOT + AlfrescoImapConst.HIERARCHY_DELIMITER + destinationName;
        String nodeContent = "test content";
        NodeRef root = findCompanyHomeNodeRef();
        AuthenticationUtil.setRunAsUserSystem();
        // Create node and destination folder
        FileInfo origFile = fileFolderService.create(root, fileName, ContentModel.TYPE_CONTENT);
        ContentWriter contentWriter = contentService.getWriter(origFile.getNodeRef(), ContentModel.PROP_CONTENT, true);
        contentWriter.setMimetype("text/plain");
        contentWriter.setEncoding("UTF-8");
        contentWriter.putContent(nodeContent);
        FileInfo destinationNode = fileFolderService.create(root, destinationName, ContentModel.TYPE_FOLDER);
        nodeService.addAspect(origFile.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT, null);
        nodeService.addAspect(origFile.getNodeRef(), ContentModel.ASPECT_TAGGABLE, null);
        // Save the message and set X-Alfresco-NodeRef-ID header
        SimpleStoredMessage origMessage = imapService.getMessage(origFile);
        origMessage.getMimeMessage().addHeader(AlfrescoImapConst.X_ALF_NODEREF_ID, origFile.getNodeRef().getId());
        origMessage.getMimeMessage().saveChanges();
        // Append the message to destination
        AlfrescoImapFolder destinationMailbox = imapService.getOrCreateMailbox(poweredUser, destinationPath, true, false);
        long uuid = destinationMailbox.appendMessage(origMessage.getMimeMessage(), flags, null);
        
        // Delete the node
        imapService.setFlag(origFile, Flags.Flag.DELETED, true);
        imapService.expungeMessage(origFile);
        // Check the destination has copy of original file and only this file
        FileInfo copiedNode = fileFolderService.getFileInfo(nodeService.getNodeRef(uuid));
        assertNotNull("The file should exist.", copiedNode);
        assertEquals("The file name should not change.", fileName, copiedNode.getName());
        NodeRef copiedParentNodeRef = nodeService.getPrimaryParent(copiedNode.getNodeRef()).getParentRef();
        assertEquals("The parent should change to destination.", destinationNode.getNodeRef(), copiedParentNodeRef);
        assertEquals("There should be only one node in the destination folder", 1, nodeService.getChildAssocs(destinationNode.getNodeRef()).size());
        assertTrue("New node should have original node aspects", nodeService.hasAspect(copiedNode.getNodeRef(), ContentModel.ASPECT_TAGGABLE));
    }
    
    
    /**
     * @param mailbox - {@link AlfrescoImapFolder} instance which should be checked
     */
    private void assertMailboxNotNull(AlfrescoImapFolder mailbox)
    {
        assertNotNull(mailbox);
        assertNotNull(mailbox.getFolderInfo());
        assertNotNull(mailbox.getFolderInfo().getNodeRef());
    }
    /**
     * @param user - {@link AlfrescoImapUser} instance, which determines a user who has enough permissions to rename a node
     * @param root - {@link NodeRef} instance, which determines Alfresco IMAP root node
     * @param targetNodePath - {@link String} value, which determines a path in IMAP notation to a node which should be renamed
     * @param targetNode - {@link FileInfo} instance, which determines a node, located at the targetNodePath path
     * @throws FileNotFoundException
     */
    private void assertMailboxRenaming(AlfrescoImapUser user, NodeRef root, String targetNodePath, List renamedNodeName, String renamedNodePath, FileInfo targetNode)
            throws FileNotFoundException
    {
        AlfrescoImapFolder mailbox = imapService.getOrCreateMailbox(user, targetNodePath, true, false);
        assertNotNull(("Just created mailbox can't be received by full path via the ImapService. Path: '" + targetNodePath + "'"), mailbox);
        assertNotNull(mailbox.getFolderInfo());
        assertNotNull(mailbox.getFolderInfo().getNodeRef());
        imapService.renameMailbox(user, targetNodePath, renamedNodePath);
        NodeRef actualNode = null;
        if (null != targetNode)
        {
            FileInfo actualFileInfo = fileFolderService.resolveNamePath(root, renamedNodeName);
            assertNotNull(actualFileInfo);
            assertNotNull(actualFileInfo.getNodeRef());
            actualNode = actualFileInfo.getNodeRef();
        }
        else
        {
            mailbox = imapService.getOrCreateMailbox(user, renamedNodePath, true, false);
            assertNotNull(mailbox);
            actualNode = mailbox.getFolderInfo().getNodeRef();
        }
        assertNotNull(("Can't receive renamed node by full path: '" + renamedNodePath + "'"), actualNode);
        if (null != targetNode)
        {
            assertEquals(targetNode.getNodeRef(), actualNode);
        }
        else
        {
            assertMailboxInUserImapHomeDirectory(root, actualNode);
        }
    }
    /**
     * @param root - {@link NodeRef} instance, which determines Alfresco IMAP root node
     * @param actualNode - {@link NodeRef} instance, which determines mailbox in actual state
     * @throws FileNotFoundException
     */
    private void assertMailboxInUserImapHomeDirectory(NodeRef root, NodeRef actualNode) throws FileNotFoundException
    {
        List path = fileFolderService.getNameOnlyPath(root, actualNode);
        int satisfactionFlag = 0;
        for (String element : path)
        {
            if (TEST_IMAP_FOLDER_NAME.equals(element) || USER_NAME.equals(element))
            {
                satisfactionFlag++;
            }
            if (satisfactionFlag > 1)
            {
                break;
            }
        }
        assertTrue(satisfactionFlag > 1);
    }
    /**
     * @param pathBeforeRenaming - {@link List}<{@link String}> instance, which represents a path to some node before some action
     * @param pathAfterRenaming - {@link List}<{@link String}> instance, which represents a path to some node after some action
     */
    private void assertPathHierarchy(List pathBeforeRenaming, List pathAfterRenaming)
    {
        assertNotNull(pathAfterRenaming);
        assertNotNull(pathBeforeRenaming);
        assertEquals(pathBeforeRenaming.size(), pathAfterRenaming.size());
        Iterator before = pathBeforeRenaming.iterator();
        Iterator after = pathAfterRenaming.iterator();
        for (int i = 0; i < (pathAfterRenaming.size() - 2); i++)
        {
            assertTrue(before.hasNext());
            assertTrue(after.hasNext());
            assertEquals(before.next(), after.next());
        }
    }
    /**
     * @return {@link NodeRef} instance of '/app:company_home' node
     */
    private NodeRef findCompanyHomeNodeRef()
    {
        NodeRef storeRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
        List nodeRefs = searchService.selectNodes(storeRootNodeRef, APP_COMPANY_HOME, null, namespaceService, false);
        return nodeRefs.get(0);
    }
    /**
     * MNT-12773
     * @throws AddressException
     */
    public void testSearchTerms() throws AddressException 
    {
        List mf = imapService.listMailboxes(user, IMAP_ROOT+"/"+TEST_IMAP_FOLDER_NAME+"/_*", false);
        ArrayList res = new ArrayList();
        SearchTerm st = null;
        st = new SentDateTerm(ComparisonTerm.LT, new Date());
        extractSearchTermResultsToList(mf, st, res);
        assertEquals("Size of mails isn't correct. Search by sent date", 3, res.size());
        res.clear();
        st = new SubjectTerm("For Test");
        extractSearchTermResultsToList(mf, st, res);
        assertEquals("Size of mails isn't correct. Search by subject", 1, res.size());
        res.clear();
        st = new FromTerm(new InternetAddress("admin@alfresco.com"));
        extractSearchTermResultsToList(mf, st, res);
        assertEquals("Size of mails isn't correct. Search by \"From\" term", 1, res.size());
        res.clear();
        st = new AndTerm(st, new SubjectTerm("For Test"));
        extractSearchTermResultsToList(mf, st, res);
        assertEquals("Size of mails isn't correct. Search by \"From\" and \"Subject\" terms", 1, res.size());
    }
    private void extractSearchTermResultsToList(List folders, SearchTerm searchTerm, ArrayList resList)
    {
        for (AlfrescoImapFolder folder : folders)
        {
            for (long l : folder.search(searchTerm))
            {
                resList.add(l);
            }
        }
    }
}