/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * 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 <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.webdav;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ContentMetadataExtracter;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.rest.api.tests.client.data.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.lock.UnableToAquireLockException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.ContentService;
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.ResultSet;
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.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap;
import org.alfresco.util.testing.category.LuceneTests;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import javax.servlet.http.HttpServletResponse;
import javax.transaction.Status;
import javax.transaction.UserTransaction;

import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Unit tests for the {@link PutMethod} class.
 *
 * @author alex.mukha
 */
@Category(LuceneTests.class)
public class PutMethodTest
{
    private static ApplicationContext ctx;

    private static final String USER1_NAME = "user1-" + PutMethodTest.class.getName();
    private static final String USER2_NAME = "user2-" + PutMethodTest.class.getName();
    private static final String TEST_DATA_FILE_NAME = "filewithdata.txt";
    private static final String DAV_LOCK_INFO_ADMIN = "davLockInfoAdmin.xml";
    private static final String DAV_LOCK_INFO_USER2 = "davLockInfoUser2.xml";
    private byte[] testDataFile;
    private byte[] davLockInfoAdminFile;
    private byte[] davLockInfoUser2File;

    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private WebDAVMethod method;
    private UserTransaction txn = null;

    private SearchService searchService;
    private FileFolderService fileFolderService;
    private NodeService nodeService;
    private TransactionService transactionService;
    private WebDAVHelper webDAVHelper;
    private MutableAuthenticationService authenticationService;
    private PersonService personService;
    private LockService lockService;
    private ContentService contentService;
    private CheckOutCheckInService checkOutCheckInService;
    private PermissionService permissionService;
    private ActionService actionService;

    private Repository repositoryHelper;
    private NodeRef companyHomeNodeRef;
    private NodeRef versionableDoc;
    private String versionableDocName;
    private StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
    private NamespaceService namespaceService;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
        ctx = ApplicationContextHelper.getApplicationContext(new String[]
            {
                "classpath:alfresco/application-context.xml",
                "classpath:alfresco/web-scripts-application-context.xml",
                "classpath:alfresco/remote-api-context.xml"
            });
    }

    @Before
    public void setUp() throws Exception
    {
        searchService = ctx.getBean("SearchService", SearchService.class);
        fileFolderService = ctx.getBean("FileFolderService", FileFolderService.class);
        nodeService = ctx.getBean("NodeService", NodeService.class);
        transactionService = ctx.getBean("transactionService", TransactionService.class);
        webDAVHelper = ctx.getBean("webDAVHelper", WebDAVHelper.class);
        authenticationService = ctx.getBean("authenticationService", MutableAuthenticationService.class);
        personService = ctx.getBean("PersonService", PersonService.class);
        lockService = ctx.getBean("LockService", LockService.class);
        contentService = ctx.getBean("contentService", ContentService.class);
        checkOutCheckInService = ctx.getBean("CheckOutCheckInService", CheckOutCheckInService.class);
        permissionService = ctx.getBean("PermissionService", PermissionService.class);
        namespaceService = ctx.getBean("namespaceService", NamespaceService.class);
        actionService = ctx.getBean("ActionService", ActionService.class);

        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());

        repositoryHelper = (Repository)ctx.getBean("repositoryHelper");
        companyHomeNodeRef = repositoryHelper.getCompanyHome();
        txn = transactionService.getUserTransaction();
        txn.begin();
        createUser(USER1_NAME);
        createUser(USER2_NAME);

        InputStream testDataIS = getClass().getClassLoader().getResourceAsStream(TEST_DATA_FILE_NAME);
        InputStream davLockInfoAdminIS = getClass().getClassLoader().getResourceAsStream(DAV_LOCK_INFO_ADMIN);
        InputStream davLockInfoUser2IS = getClass().getClassLoader().getResourceAsStream(DAV_LOCK_INFO_USER2);
        
        testDataFile = IOUtils.toByteArray(testDataIS);
        davLockInfoAdminFile = IOUtils.toByteArray(davLockInfoAdminIS);
        davLockInfoUser2File = IOUtils.toByteArray(davLockInfoUser2IS);

        testDataIS.close();
        davLockInfoAdminIS.close();
        davLockInfoUser2IS.close();

        // Create a test file with versionable aspect and content
        Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
        versionableDocName = "doc-" + GUID.generate();
        properties.put(ContentModel.PROP_NAME, versionableDocName);

        versionableDoc = nodeService.createNode(companyHomeNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName(ContentModel.USER_MODEL_URI, versionableDocName),
                ContentModel.TYPE_CONTENT, properties).getChildRef();
        contentService.getWriter(versionableDoc, ContentModel.PROP_CONTENT, true).putContent("WebDAVTestContent");
        nodeService.addAspect(versionableDoc, ContentModel.ASPECT_VERSIONABLE, null);

        txn.commit();

        txn = transactionService.getUserTransaction();
        txn.begin();
    }

    private void createUser(String userName)
    {
        if (!authenticationService.authenticationExists(userName))
        {
            authenticationService.createAuthentication(userName, "PWD".toCharArray());
        }

        if (!personService.personExists(userName))
        {
            PropertyMap ppOne = new PropertyMap();
            ppOne.put(ContentModel.PROP_USERNAME, userName);
            ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");
            ppOne.put(ContentModel.PROP_LASTNAME, "lastName");
            ppOne.put(ContentModel.PROP_EMAIL, "[email protected]");
            ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");

            personService.createPerson(ppOne);
        }
    }

    @After
    public void tearDown() throws Exception
    {
        method = null;
        request = null;
        response = null;
        testDataFile = null;
        davLockInfoAdminFile = null;

        if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK)
        {
            txn.rollback();
        }
        else
        {
            txn.commit();
        }
        
        AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
        deleteUser(USER1_NAME);
        deleteUser(USER2_NAME);

        nodeService.deleteNode(versionableDoc);

        // As per MNT-10037 try to create a node and delete it in the next txn
        txn = transactionService.getUserTransaction();
        txn.begin();

        Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
        String nodeName = "leak-session-doc-" + GUID.generate();
        properties.put(ContentModel.PROP_NAME, nodeName);

        NodeRef nodeRef = nodeService.createNode(companyHomeNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName(ContentModel.USER_MODEL_URI, nodeName),
                ContentModel.TYPE_CONTENT, properties).getChildRef();
        contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true).putContent("WebDAVTestContent");

        txn.commit();

        txn = transactionService.getUserTransaction();
        txn.begin();

        nodeService.deleteNode(nodeRef);

        txn.commit();

        AuthenticationUtil.clearCurrentSecurityContext();
    }

    private void deleteUser(String userName)
    {
        // Delete the user, as admin
        if (this.personService.personExists(userName))
        {
            this.personService.deletePerson(userName);
        }
        if (this.authenticationService.authenticationExists(userName))
        {
            this.authenticationService.deleteAuthentication(userName);
        }
    }

    @Test
    public void testPutContentToNonExistingFile() throws Exception
    {
        String fileName = "file-" + GUID.generate();
        NodeRef fileNoderef = null;
        try
        {
            executeMethod(WebDAV.METHOD_PUT, fileName, testDataFile, null);

            List<NodeRef> refs = searchService.selectNodes(nodeService.getRootNode(storeRef),
                "/app:company_home/cm:" + fileName , null, namespaceService, false);
            fileNoderef = refs.get(0);

            assertTrue("File does not exist.", nodeService.exists(fileNoderef));
            assertEquals("Filename is not correct", fileName, nodeService.getProperty(fileNoderef, ContentModel.PROP_NAME));
            assertTrue("Expected return status is " + HttpServletResponse.SC_CREATED + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_CREATED == response.getStatus());
            InputStream updatedFileIS = fileFolderService.getReader(fileNoderef).getContentInputStream();
            byte[] updatedFile = IOUtils.toByteArray(updatedFileIS);
            updatedFileIS.close();
            assertTrue("The content has to be equal", ArrayUtils.isEquals(testDataFile, updatedFile));
        }
        catch (Exception e)
        {
            fail("Failed to upload a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }
        finally
        {
            if (fileNoderef != null)
            {
                nodeService.deleteNode(fileNoderef);
            }
        }
    }

    @Test
    public void testPutContentToAnExistingFile() throws Exception
    {
        FileInfo testFileInfo = fileFolderService.create(companyHomeNodeRef, "file-" + GUID.generate(), ContentModel.TYPE_CONTENT);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, testFileInfo.getName(), testDataFile, null);

            assertTrue("File does not exist.", nodeService.exists(testFileInfo.getNodeRef()));
            assertEquals("Filename is not correct.", testFileInfo.getName(), nodeService.getProperty(testFileInfo.getNodeRef(), ContentModel.PROP_NAME));
            assertTrue("Expected return status is " + HttpServletResponse.SC_NO_CONTENT + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_NO_CONTENT == response.getStatus());
            InputStream updatedFileIS = fileFolderService.getReader(testFileInfo.getNodeRef()).getContentInputStream();
            byte[] updatedFile = IOUtils.toByteArray(updatedFileIS);
            updatedFileIS.close();
            assertTrue("The content has to be equal", ArrayUtils.isEquals(testDataFile, updatedFile));
        }
        catch (Exception e)
        {
            fail("Failed to upload a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }
        finally
        {
            nodeService.deleteNode(testFileInfo.getNodeRef());
        }
    }

    /**
     * Updating a file in an non-existent path
     */
    @Test
    public void testPutContentBadPath() throws Exception
    {
        String fileName = "file-" + GUID.generate();
        NodeRef fileNoderef = null;
        try
        {
            // Add non-existent path
            executeMethod(WebDAV.METHOD_PUT, "non/existent/path" + fileName, testDataFile, null);

            fail("The PUT execution should fail with a 400 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue(wse.getHttpStatusCode() == HttpServletResponse.SC_CONFLICT);
        }
        catch (Exception e)
        {
            fail("Failed to upload a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }
        finally
        {
            if (fileNoderef != null)
            {
                nodeService.deleteNode(fileNoderef);
            }
        }
    }

    /**
     * Creating a folder and trying to update it with a file
     */
    @Test
    public void testPutContentToFolder() throws Exception
    {
        FileInfo testFileInfo = fileFolderService.create(companyHomeNodeRef, "folder-" + GUID.generate(), ContentModel.TYPE_FOLDER);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, testFileInfo.getName(), testDataFile, null);

            fail("The PUT execution should fail with a 400 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue(wse.getHttpStatusCode() == HttpServletResponse.SC_BAD_REQUEST);
        }
        catch (Exception e)
        {
            fail("Failed to upload a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }
        finally
        {
            nodeService.deleteNode(testFileInfo.getNodeRef());
        }
    }

    /**
     * Putting a content to a locked file
     * <p>
     * Create and lock a file by admin
     * <p>
     * Try to put the content by user
     */
    @SuppressWarnings("deprecation")
    @Test
    public void testPutContentToLockedFIle() throws Exception
    {
        FileInfo testFileInfo = fileFolderService.create(companyHomeNodeRef, "file-" + GUID.generate(), ContentModel.TYPE_CONTENT);
        lockService.lock(testFileInfo.getNodeRef(), LockType.WRITE_LOCK);
        try
        {
            AuthenticationUtil.setFullyAuthenticatedUser(USER1_NAME);
            executeMethod(WebDAV.METHOD_PUT, testFileInfo.getName(), testDataFile, null);

            fail("The PUT execution should fail with a 423 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue(wse.getHttpStatusCode() == WebDAV.WEBDAV_SC_LOCKED);
        }
        catch (Exception e)
        {
            fail("Failed to upload a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }
        finally
        {
            AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
            nodeService.deleteNode(testFileInfo.getNodeRef());
        }
    }

    /**
     * Putting a content to a working copy file
     * <p>
     * Create and check out a file by user1
     * <p>
     * Try to put the content to the working copy by user2
     * 
     * See MNT-8614.
     */
    @SuppressWarnings("deprecation")
    @Test
    public void testPutContentToWorkingCopy() throws Exception
    {
        FileInfo folder = fileFolderService.create(companyHomeNodeRef, "folder-" + GUID.generate(), ContentModel.TYPE_FOLDER);
        permissionService.setInheritParentPermissions(folder.getNodeRef(), false);
        permissionService.setPermission(folder.getNodeRef(), USER1_NAME, permissionService.getAllPermission(), true);

        AuthenticationUtil.setFullyAuthenticatedUser(USER1_NAME);
        FileInfo testFileInfo = fileFolderService.create(folder.getNodeRef(), "file-" + GUID.generate(), ContentModel.TYPE_CONTENT);
        NodeRef workingCopyNodeRef = checkOutCheckInService.checkout(testFileInfo.getNodeRef());
        String workingCopyName = fileFolderService.getFileInfo(workingCopyNodeRef).getName();
        String pathToWC = "/" + folder.getName() + "/" + workingCopyName;
        String pathToOriginal = "/" + folder.getName() + "/" + testFileInfo.getName();

        // Negative test, try to edit the WC without permissions.
        AuthenticationUtil.setFullyAuthenticatedUser(USER2_NAME);
        try
        {
            lockService.lock(workingCopyNodeRef, LockType.WRITE_LOCK);
        }
        catch (AccessDeniedException ade)
        {
            // expected
        }

        try
        {
            executeMethod(WebDAV.METHOD_LOCK, pathToWC, davLockInfoUser2File, null);

            fail("The LOCK execution should fail with a 401 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue("The status code was " + wse.getHttpStatusCode() + ", but should be " + HttpServletResponse.SC_UNAUTHORIZED,
                    wse.getHttpStatusCode() == HttpServletResponse.SC_UNAUTHORIZED);
        }
        catch (Exception e)
        {
            fail("Unexpected exception occurred: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        // Construct IF HEADER
        String lockToken = workingCopyNodeRef.getId() + WebDAV.LOCK_TOKEN_SEPERATOR + USER2_NAME;
        String lockHeaderValue = "(<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">)";
        HashMap<String, String> headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_IF, lockHeaderValue);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, pathToWC, testDataFile, headers);
            fail("The PUT execution should fail with a 423 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue("The status code was " + wse.getHttpStatusCode() + ", but should be " + HttpServletResponse.SC_UNAUTHORIZED,
                    wse.getHttpStatusCode() == HttpServletResponse.SC_UNAUTHORIZED);
        }
        catch (Exception e)
        {
            fail("Unexpected exception occurred: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        // Positive test
        AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
        permissionService.setPermission(folder.getNodeRef(), USER2_NAME, permissionService.getAllPermission(), true);

        AuthenticationUtil.setFullyAuthenticatedUser(USER2_NAME);
        try
        {
            executeMethod(WebDAV.METHOD_LOCK, pathToWC, davLockInfoUser2File, null);

            assertEquals("File should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(workingCopyNodeRef));
        }
        catch (Exception e)
        {
            fail("Failed to lock a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_IF, lockHeaderValue);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, pathToWC, testDataFile, headers);

            assertTrue("File does not exist.", nodeService.exists(workingCopyNodeRef));
            assertEquals("Filename is not correct", workingCopyName, nodeService.getProperty(workingCopyNodeRef, ContentModel.PROP_NAME));
            assertTrue("Expected return status is " + HttpServletResponse.SC_NO_CONTENT + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_NO_CONTENT == response.getStatus());

            assertTrue("File should have NO_CONTENT aspect", nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_NO_CONTENT));
            InputStream updatedFileIS = fileFolderService.getReader(workingCopyNodeRef).getContentInputStream();
            byte[] updatedFile = IOUtils.toByteArray(updatedFileIS);
            updatedFileIS.close();
            assertTrue("The content has to be equal", ArrayUtils.isEquals(testDataFile, updatedFile));
        }
        catch (Exception e)
        {
            fail("Failed to upload a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_LOCK_TOKEN, "<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">");
        try
        {
            executeMethod(WebDAV.METHOD_UNLOCK, pathToWC, null, headers);

            assertTrue("Expected return status is " + HttpServletResponse.SC_NO_CONTENT + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_NO_CONTENT == response.getStatus());
            assertFalse("File should not have NO_CONTENT aspect", nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_NO_CONTENT));
            assertEquals("File should be unlocked", LockStatus.NO_LOCK, lockService.getLockStatus(workingCopyNodeRef));
        }
        catch (Exception e)
        {
            fail("Failed to unlock a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        // Negative test try to lock or edit the original file
        AuthenticationUtil.setFullyAuthenticatedUser(USER2_NAME);
        try
        {
            lockService.lock(testFileInfo.getNodeRef(), LockType.WRITE_LOCK);
        }
        catch (UnableToAquireLockException uale)
        {
            // expected
        }

        try
        {
            executeMethod(WebDAV.METHOD_LOCK, pathToOriginal, davLockInfoUser2File, null);

            fail("The LOCK execution should fail with a 423 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue("The status code was " + wse.getHttpStatusCode() + ", but should be " + WebDAV.WEBDAV_SC_LOCKED, wse.getHttpStatusCode() == WebDAV.WEBDAV_SC_LOCKED);
        }
        catch (Exception e)
        {
            fail("Unexpected exception occurred: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        // Construct IF HEADER
        lockToken = testFileInfo.getNodeRef().getId() + WebDAV.LOCK_TOKEN_SEPERATOR + USER2_NAME;
        lockHeaderValue = "(<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">)";
        headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_IF, lockHeaderValue);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, pathToOriginal, testDataFile, headers);
            fail("The PUT execution should fail with a 423 error");
        }
        catch (WebDAVServerException wse)
        {
            // The execution failed and it is expected
            assertTrue("The status code was " + wse.getHttpStatusCode() + ", but should be " + WebDAV.WEBDAV_SC_LOCKED, wse.getHttpStatusCode() == WebDAV.WEBDAV_SC_LOCKED);
        }
        catch (Exception e)
        {
            fail("Unexpected exception occurred: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        AuthenticationUtil.setFullyAuthenticatedUser(USER1_NAME);
        checkOutCheckInService.checkin(workingCopyNodeRef, null);

        AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
        nodeService.deleteNode(folder.getNodeRef());
    }
    
    /**
     * Putting a content and check versioning
     * <p>
     * The node will be deleted in after test.
     */
    @Test
    public void testPutContentCheckVersions() throws Exception
    {
        assertEquals("The version should be 1.0 as no file modifications were done yet.", "1.0", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));

        // Lock the node
        try
        {
            executeMethod(WebDAV.METHOD_LOCK, versionableDocName, davLockInfoAdminFile, null);

            assertEquals("The version should not advance", "1.0", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));
        }
        catch (Exception e)
        {
            fail("Failed to lock a file: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
        }

        // Split to transactions to check the commit
        txn.commit();
        txn = transactionService.getUserTransaction();
        txn.begin();
        assertEquals("The version should not advance", "1.0", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));

        // Put non-zero content
        // Construct IF HEADER
        String lockToken = versionableDoc.getId() + WebDAV.LOCK_TOKEN_SEPERATOR + AuthenticationUtil.getAdminUserName();
        String lockHeaderValue = "(<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">)";
        HashMap<String, String> headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_IF, lockHeaderValue);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, versionableDocName, testDataFile, headers);

            assertEquals("The version should not advance", "1.0", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to upload a file", e);
        }

        // Split to transactions to check the commit
        txn.commit();
        txn = transactionService.getUserTransaction();
        txn.begin();
        assertEquals("The version should advance", "1.1", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));

        // Unlock
        headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_LOCK_TOKEN, "<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">");
        try
        {
            executeMethod(WebDAV.METHOD_UNLOCK, versionableDocName, null, headers);

            assertEquals("The version should not advance from 1.1", "1.1", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to unlock a file", e);
        }

        // Split to transactions to check the commit
        txn.commit();
        txn = transactionService.getUserTransaction();
        txn.begin();
        assertEquals("The version should not advance from 1.1", "1.1", nodeService.getProperty(versionableDoc, ContentModel.PROP_VERSION_LABEL));
    }

    /**
     * Putting a zero content file and update it
     * <p>
     * Put an empty file
     * <p>
     * Lock the file
     * <p>
     * Put the contents
     * <p>
     * Unlock the node
     */
    @Test
    public void testPutNoContentFileAndUpdate() throws Exception
    {
        String fileName = "file-" + GUID.generate();
        NodeRef fileNoderef = null;
        try
        {
            executeMethod(WebDAV.METHOD_PUT, fileName, new byte[0], null);

            List<NodeRef> refs = searchService.selectNodes(nodeService.getRootNode(storeRef),
                "/app:company_home/cm:" + fileName , null, namespaceService, false);
            fileNoderef = refs.get(0);

            assertTrue("File should exist.", nodeService.exists(fileNoderef));
            assertEquals("Filename is not correct", fileName, nodeService.getProperty(fileNoderef, ContentModel.PROP_NAME));

            assertTrue("Expected return status is " + HttpServletResponse.SC_CREATED + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_CREATED == response.getStatus());
            byte[] updatedFile = IOUtils.toByteArray(fileFolderService.getReader(fileNoderef).getContentInputStream());
            assertTrue("The content should be empty", updatedFile.length == 0);
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to upload a file", e);
        }

        try
        {
            executeMethod(WebDAV.METHOD_LOCK, fileName, davLockInfoAdminFile, null);

            assertEquals("File should be locked", LockStatus.LOCK_OWNER, lockService.getLockStatus(fileNoderef));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to lock a file", e);
        }

        // Construct IF HEADER
        String lockToken = fileNoderef.getId() + WebDAV.LOCK_TOKEN_SEPERATOR + AuthenticationUtil.getAdminUserName();
        String lockHeaderValue = "(<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">)";
        HashMap<String, String> headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_IF, lockHeaderValue);
        try
        {
            executeMethod(WebDAV.METHOD_PUT, fileName, testDataFile, headers);

            assertTrue("File does not exist.", nodeService.exists(fileNoderef));
            assertEquals("Filename is not correct", fileName, nodeService.getProperty(fileNoderef, ContentModel.PROP_NAME));
            assertTrue("Expected return status is " + HttpServletResponse.SC_NO_CONTENT + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_NO_CONTENT == response.getStatus());

            assertTrue("File should have NO_CONTENT aspect", nodeService.hasAspect(fileNoderef, ContentModel.ASPECT_NO_CONTENT));
            InputStream updatedFileIS = fileFolderService.getReader(fileNoderef).getContentInputStream();
            byte[] updatedFile = IOUtils.toByteArray(updatedFileIS);
            updatedFileIS.close();
            assertTrue("The content has to be equal", ArrayUtils.isEquals(testDataFile, updatedFile));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to upload a file", e);
        }

        headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_LOCK_TOKEN, "<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">");
        try
        {
            executeMethod(WebDAV.METHOD_UNLOCK, fileName, null, headers);

            assertTrue("Expected return status is " + HttpServletResponse.SC_NO_CONTENT + ", but returned is " + response.getStatus(),
                    HttpServletResponse.SC_NO_CONTENT == response.getStatus());
            assertFalse("File should not have NO_CONTENT aspect", nodeService.hasAspect(fileNoderef, ContentModel.ASPECT_NO_CONTENT));
            assertEquals("File should be unlocked", LockStatus.NO_LOCK, lockService.getLockStatus(fileNoderef));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to unlock a file", e);
        }

        if (fileNoderef != null)
        {
            nodeService.deleteNode(fileNoderef);
        }
    }

    /**
     * Negative test to check that a temp file will be deleted from the repo if writing the content fails
     */
    @Test
    public void testPutNullContent() throws Exception
    {
        String fileName = "file-" + GUID.generate();
        NodeRef fileNoderef = null;

        // Lock the node
        try
        {
            executeMethod(WebDAV.METHOD_LOCK, fileName, davLockInfoAdminFile, null);
            List<NodeRef> refs = searchService.selectNodes(nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE),
                "/app:company_home/cm:" + fileName , null, namespaceService, false);
            fileNoderef = refs.get(0);
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to lock a file", e);
        }

        txn.commit();
        txn = transactionService.getUserTransaction();
        txn.begin();

        // Construct IF HEADER
        String lockToken = fileNoderef.getId() + WebDAV.LOCK_TOKEN_SEPERATOR + AuthenticationUtil.getAdminUserName();
        String lockHeaderValue = "(<" + WebDAV.OPAQUE_LOCK_TOKEN + lockToken + ">)";
        HashMap<String, String> headers = new HashMap<String, String>();
        headers.put(WebDAV.HEADER_IF, lockHeaderValue);
        // Simulate any failure during the content write
        // null content is no longer null in MockHttpServletRequest (Spring 5)
        ActionService mockActionService = mock(ActionService.class);
        when(mockActionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME))
                .thenThrow(new AlfrescoRuntimeException("Negative test"));
        try
        {
            webDAVHelper.setActionService(mockActionService);
            // setting a null content
            executeMethod(WebDAV.METHOD_PUT, fileName, null, headers);

            fail("The execution should fail.");
        }
        catch (WebDAVServerException wse)
        {
            if (nodeService.exists(fileNoderef))
            {
                nodeService.deleteNode(fileNoderef);
                fail("File exist, but should not.");
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to upload a file", e);
        }
        finally
        {
            webDAVHelper.setActionService(actionService);
        }

        if (fileNoderef != null && nodeService.exists(fileNoderef))
        {
            nodeService.deleteNode(fileNoderef);
        }
    }

    /**
     * Executes WebDAV method for testing
     * <p>
     * Sets content to request from a test file
     * 
     * @param methodName Method to prepare, should be initialized (PUT, LOCK, UNLOCK are supported)
     * @param fileName the name of the file set to the context, can be used with path, i.e. "path/to/file/fileName.txt"
     * @param content If <b>not null</b> adds test content to the request
     * @param headers to set to request, can be null
     * @throws Exception
     */
    private void executeMethod(String methodName, String fileName, byte[] content, Map<String, String> headers) throws Exception
    {
        if (methodName == WebDAV.METHOD_PUT)
            method = new PutMethod();
        else if (methodName == WebDAV.METHOD_LOCK)
            method = new LockMethod();
        else if (methodName == WebDAV.METHOD_UNLOCK)
            method = new UnlockMethod();
        if (method != null)
        {
            request = new MockHttpServletRequest(methodName, "/alfresco/webdav/" + fileName);
            response = new MockHttpServletResponse();
            request.setServerPort(8080);
            request.setServletPath("/webdav");
            if (content != null)
            {
                request.setContent(content);
            }

            if (headers != null && !headers.isEmpty())
            {
                for (String key : headers.keySet())
                {
                    request.addHeader(key, headers.get(key));
                }
            }

            method.setDetails(request, response, webDAVHelper, companyHomeNodeRef);

            method.execute();
        }
    }
}