/*
 * Copyright 2011 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jbpm.formbuilder.server;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.easymock.EasyMock;
import org.jboss.resteasy.util.HttpHeaderNames;
import org.jbpm.formbuilder.server.file.FileException;
import org.jbpm.formbuilder.server.file.FileService;
import org.jbpm.formbuilder.server.xml.FileListDTO;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class RESTFileServiceTest extends RESTAbstractTest {

    public void testSetContextOK() throws Exception {
        RESTFileService restService = new RESTFileService();
        URL pathToClasses = getClass().getResource("/FormBuilder.properties");
        String filePath = pathToClasses.toExternalForm();
        //assumes compilation is in target/classes
        filePath = filePath.replace("target/classes/FormBuilder.properties", "src/main/webapp");
        filePath = filePath + "/WEB-INF/springComponents.xml";
        FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext(filePath);
        ServiceFactory.getInstance().setBeanFactory(ctx);
        ServletContext context = EasyMock.createMock(ServletContext.class);

        EasyMock.replay(context);
        restService.setContext(context);
        EasyMock.verify(context);

        FileService service = restService.getFileService();
        assertNotNull("service shouldn't be null", service);
    }

    public void testError() throws Exception {
        RESTFileService restService = new RESTFileService();
        
        String msg = "mock error message";
        Exception err = new NullPointerException();
        
        Response resp0 = restService.error(null, null);
        assertNotNull("resp0 shouldn't be null", resp0);
        assertStatus(resp0.getStatus(), Status.INTERNAL_SERVER_ERROR);
        Response resp1 = restService.error(msg, null);
        assertNotNull("resp1 shouldn't be null", resp1);
        assertStatus(resp1.getStatus(), Status.INTERNAL_SERVER_ERROR);
        Response resp2 = restService.error(msg, err);
        assertNotNull("resp2 shouldn't be null", resp2);
        assertStatus(resp2.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }
    
    //test happy path of RESTFileService.deleteFile(...)
    public void testDeleteFileOK() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        fileService.deleteFile(EasyMock.same("somePackage"), EasyMock.same("myFile.tmp"));
        EasyMock.expectLastCall().once();
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.deleteFile((HttpServletRequest) mocks[0], "somePackage", "myFile.tmp");
        EasyMock.verify(mocks);
        
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.NO_CONTENT);
        
    }
    
    //test response to a FileException of RESTFileService.deleteFile(...)
    public void testDeleteFileServiceProblem() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        FileException exception = new FileException("Something went wrong", new NullPointerException());
        fileService.deleteFile(EasyMock.same("somePackage"), EasyMock.same("myFile.tmp"));
        EasyMock.expectLastCall().andThrow(exception).once();
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.deleteFile((HttpServletRequest) mocks[0], "somePackage", "myFile.tmp");
        EasyMock.verify(mocks);
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }
    
    //test happy path for RESTFileService.getFiles(...) returning files
    public void testGetFilesOK() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        List<String> retval = new ArrayList<String>();
        retval.add("myFile1.tmp");
        retval.add("myFile2.tmp");
        EasyMock.expect(fileService.loadFilesByType(EasyMock.same("somePackage"), EasyMock.same("tmp"))).
            andReturn(retval);
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.getFiles((HttpServletRequest) mocks[0], "somePackage", new String[] {"tmp"}); 
        EasyMock.verify(mocks);
        Object objDto = assertXmlOkResponse(resp);
        assertTrue("objDto should be of type FileListDTO", objDto instanceof FileListDTO);
        FileListDTO dto = (FileListDTO) objDto;
        assertNotNull("dto.getFile() shouldn't be null", dto.getFile());
        for (String file : dto.getFile()) {
            assertTrue("retval should contain " + file, retval.contains(file));
        }
        for (String file : retval) {
            assertTrue("dto.getFile() should contain " + file, dto.getFile().contains(file));
        }
    }
    
    //test happy path for RESTFileService.getFiles(...) returning no files
    public void testGetFilesEmpty() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        List<String> retval = new ArrayList<String>();
        EasyMock.expect(fileService.loadFilesByType(EasyMock.same("somePackage"), EasyMock.same("tmp"))).
            andReturn(retval);
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.getFiles((HttpServletRequest) mocks[0], "somePackage", new String[] {"tmp"}); 
        EasyMock.verify(mocks);
        Object objDto = assertXmlOkResponse(resp);
        assertTrue("objDto should be of type FileListDTO", objDto instanceof FileListDTO);
        FileListDTO dto = (FileListDTO) objDto;
        assertNotNull("dto.getFile() shouldn't be null", dto.getFile());
        assertTrue("dto.getFile() should be empty", dto.getFile().isEmpty());
    }
    
    //test response to a FileException of RESTFileService.getFiles(...)
    public void testGetFilesServiceProblem() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        FileException exception = new FileException();
        EasyMock.expect(fileService.loadFilesByType(EasyMock.same("somePackage"), EasyMock.same("tmp"))).
            andThrow(exception);
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.getFiles((HttpServletRequest) mocks[0], "somePackage", new String[] {"tmp"}); 
        EasyMock.verify(mocks);
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }
    
    //test happy path for RESTFileService.getFile(...)
    public void testGetFileOK() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        byte[] myContent = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
        EasyMock.expect(fileService.loadFile(EasyMock.same("somePackage"), EasyMock.same("myFile.tmp"))).
            andReturn(myContent);
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.getFile((HttpServletRequest) mocks[0], "somePackage", "myFile.tmp");
        EasyMock.verify(mocks);
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.OK);
        assertNotNull("resp.entity shouldn't be null", resp.getEntity());
        assertNotNull("resp.metadata shouldn't be null", resp.getMetadata());
        Object contentType = resp.getMetadata().getFirst(HttpHeaderNames.CONTENT_TYPE);
        assertNotNull("resp.metadata[Content-Type] shouldn't be null", contentType);
        assertEquals("resp.metadata[Content-Type] should be application/octet-stream but is " + contentType, 
                contentType, MediaType.APPLICATION_OCTET_STREAM);
        Object objDto = resp.getEntity();
        assertTrue("objDto should be an array", objDto.getClass().isArray());
        assertTrue("objDto should be a byte array", objDto instanceof byte[]);
        byte[] retval = (byte[]) objDto;
        assertEquals("retval should be the same as " + myContent + " but is " + retval, retval, myContent);
    }
    
    //test response to a FileException for RESTFileService.getFile(...)
    public void testGetFileServiceProblem() throws Exception {
        RESTFileService restService = new RESTFileService();
        List<Object> requestMocks = createRequestMocks();
        FileService fileService = EasyMock.createMock(FileService.class);
        FileException exception = new FileException(new NullPointerException());
        EasyMock.expect(fileService.loadFile(EasyMock.same("somePackage"), EasyMock.same("myFile.tmp"))).
            andThrow(exception);
        requestMocks.add(fileService);
        restService.setFileService(fileService);
        Object[] mocks = requestMocks.toArray();
        EasyMock.replay(mocks);
        Response resp = restService.getFile((HttpServletRequest) mocks[0], "somePackage", "myFile.tmp");
        EasyMock.verify(mocks);
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }
    
    public void testSaveFileNotMultipart() throws Exception {
        RESTFileService restService = createSaveFileMockService(null, null, null, false);
        FileService fileService = EasyMock.createMock(FileService.class);
        restService.setFileService(fileService);
        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);
        EasyMock.expect(request.getSession()).andReturn(session).once();
        ServletContext context = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(session.getServletContext()).andReturn(context).once();
        
        EasyMock.replay(fileService, request, session, context);
        Response resp = restService.saveFile("somePackage", request);
        EasyMock.verify(fileService, request, session, context);
        
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }
    
    public void testSaveFileOK() throws Exception {
        byte[] bstream = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9};
        String fileName = "fileName";        
        RESTFileService restService = createSaveFileMockService(bstream, fileName, null, true);
        FileService fileService = EasyMock.createMock(FileService.class);
        String url = "http://www.redhat.com";
        EasyMock.expect(fileService.storeFile(EasyMock.eq("somePackage"), EasyMock.eq("fileName"), EasyMock.same(bstream))).
            andReturn(url).once();
        restService.setFileService(fileService);
        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);
        EasyMock.expect(request.getSession()).andReturn(session).once();
        ServletContext context = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(session.getServletContext()).andReturn(context).once();
        
        EasyMock.replay(fileService, request, session, context);
        Response resp = restService.saveFile("somePackage", request);
        EasyMock.verify(fileService, request, session, context);
        
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.OK);
        assertNotNull("resp.entity shouldn't be null", resp.getEntity());
        Object entity = resp.getEntity();
        assertNotNull("resp.metadata shouldn't be null", resp.getMetadata());
        Object contentType = resp.getMetadata().getFirst(HttpHeaderNames.CONTENT_TYPE);
        assertNotNull("resp.entity shouldn't be null", contentType);
        assertEquals("contentType should be application/xml but is" + contentType, contentType, MediaType.TEXT_PLAIN);
        String retval = entity.toString();
        assertTrue("retval should contain url", retval.contains(url));
    }

    private RESTFileService createSaveFileMockService(final byte[] bstream, final String fileName, 
            final Class<?> exceptionType, final boolean isMultipart) {
        RESTFileService restService = new RESTFileService() {
            @Override
            protected boolean isMultipart(HttpServletRequest request) {
                return isMultipart;
            }
            @Override
            protected ServletFileUpload createFileUpload() {
                return null;
            }
            @Override
            protected List<?> parseFiles(HttpServletRequest request, ServletFileUpload upload) throws FileUploadException {
                if (exceptionType != null && exceptionType.equals(FileUploadException.class)) {
                    throw new FileUploadException();
                }
                List<FileItem> retval = new ArrayList<FileItem>();
                retval.add(new DiskFileItem("fieldName", "application/octet-stream", true, fileName, 0, null));
                return retval;
            }
            @Override
            protected byte[] readItem(FileItem item) throws IOException {
                if (exceptionType != null && exceptionType.equals(IOException.class)) {
                    throw new IOException("mock io error");
                }
                return bstream;
            }
        };
        return restService;
    }
    
    public void testSaveFileServiceProblem() throws Exception {
        byte[] bstream = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9};
        String fileName = "fileName";        
        RESTFileService restService = createSaveFileMockService(bstream, fileName, null, true);
        FileService fileService = EasyMock.createMock(FileService.class);
        FileException exception = new FileException();
        EasyMock.expect(fileService.storeFile(EasyMock.eq("somePackage"), EasyMock.eq("fileName"), EasyMock.same(bstream))).
            andThrow(exception).once();
        restService.setFileService(fileService);
        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);
        EasyMock.expect(request.getSession()).andReturn(session).once();
        ServletContext context = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(session.getServletContext()).andReturn(context).once();
        
        EasyMock.replay(fileService, request, session, context);
        Response resp = restService.saveFile("somePackage", request);
        EasyMock.verify(fileService, request, session, context);
        
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }

    public void testSaveFileUploadProblem() throws Exception {
        byte[] bstream = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9};
        String fileName = "fileName";        
        RESTFileService restService = createSaveFileMockService(bstream, fileName, FileUploadException.class, true);
        FileService fileService = EasyMock.createMock(FileService.class);
        restService.setFileService(fileService);
        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);
        EasyMock.expect(request.getSession()).andReturn(session).once();
        ServletContext context = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(session.getServletContext()).andReturn(context).once();
        
        EasyMock.replay(fileService, request, session, context);
        Response resp = restService.saveFile("somePackage", request);
        EasyMock.verify(fileService, request, session, context);
        
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }

    public void testSaveFileIOProblem() throws Exception {
        byte[] bstream = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9};
        String fileName = "fileName";        
        RESTFileService restService = createSaveFileMockService(bstream, fileName, IOException.class, true);
        FileService fileService = EasyMock.createMock(FileService.class);
        restService.setFileService(fileService);
        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);
        EasyMock.expect(request.getSession()).andReturn(session).once();
        ServletContext context = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(session.getServletContext()).andReturn(context).once();
        
        EasyMock.replay(fileService, request, session, context);
        Response resp = restService.saveFile("somePackage", request);
        EasyMock.verify(fileService, request, session, context);
        
        assertNotNull("resp shouldn't be null", resp);
        assertStatus(resp.getStatus(), Status.INTERNAL_SERVER_ERROR);
    }

    private List<Object> createRequestMocks() {
        List<Object> requestMocks = new ArrayList<Object>();
        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);
        ServletContext context = EasyMock.createMock(ServletContext.class);
        EasyMock.expect(request.getSession()).andReturn(session).once();
        EasyMock.expect(session.getServletContext()).andReturn(context).once();
        requestMocks.add(request);
        requestMocks.add(session);
        requestMocks.add(context);
        return requestMocks;
    }
}
