/*
 * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License.
 *     A copy of the License is located at
 *
 *         http://aws.amazon.com/apache2.0/
 *
 *     or in the "license" file accompanying this file.
 *     This file 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.
 *
 *  Portions copyright Copyright 2002-2016 JUnit. All Rights Reserved. Copyright (c) 2007 Mockito contributors. Copyright 2004-2011 Oracle Corporation. Copyright 2010 Srikanth Reddy Lingala.
 *  Please see LICENSE.txt for applicable license terms and NOTICE.txt for applicable notices.
 */

import com.amazonaws.services.codebuild.model.InvalidInputException;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import enums.EncryptionAlgorithm;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.scm.SCM;
import net.lingala.zip4j.ZipFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipOutputStream;

import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class S3DataManagerTest {

    private AmazonS3Client s3Client = mock(AmazonS3Client.class);
    private static final String mockWorkspaceDir = "/tmp/jenkins/workspace/proj";
    private FilePath testWorkSpace = new FilePath(new File(mockWorkspaceDir));
    private FilePath testZipSourceWorkspace = new FilePath(new File("/"));
    Map<String, String> s3ARNs = new HashMap<>();
    private String s3InputBucketName = "Inputbucket";
    private String s3InputKeyName = "InputKey";
    private String sseAlgorithm = EncryptionAlgorithm.AES256.toString();
    private String localSourcePath = "";
    private String workspaceSubdir = "";

    AbstractBuild build = mock(AbstractBuild.class);
    BuildListener listener = mock(BuildListener.class);

    //mock console log
    private ByteArrayOutputStream log;

    @Before
    public void setUp() throws IOException, InterruptedException {
        //set the CodeBuilder instance to write its messages to the log here so
        //we can read what it prints.
        log = new ByteArrayOutputStream();
        PrintStream p = new PrintStream(log);
        when(listener.getLogger()).thenReturn(p);

        clearTestDirectories();
        testWorkSpace.mkdirs();
    }

    private S3DataManager createDefault() {
        return new S3DataManager(s3Client, s3InputBucketName, s3InputKeyName, sseAlgorithm, localSourcePath, workspaceSubdir);
    }

    //creates S3DataManager with parameters that won't throw a FileNotFoundException for below tests.
    private S3DataManager createDefaultSource(String localSourcePath, String workspaceSubdir) {
        this.s3ARNs.put("main", "ARN1/bucket/thing.zip"); //put one item in s3ARNs so exception doesn't happen.

        PutObjectResult mockedResponse = new PutObjectResult();
        mockedResponse.setVersionId("some-version-id");
        when(s3Client.putObject(any(PutObjectRequest.class))).thenReturn(mockedResponse);
        return new S3DataManager(s3Client, s3InputBucketName, s3InputKeyName, sseAlgorithm, localSourcePath, workspaceSubdir);
    }

    private void clearTestDirectories() throws IOException {
        File jenkinsDir = new File(mockWorkspaceDir);
        if(jenkinsDir.exists()) {
            FileUtils.cleanDirectory(jenkinsDir);
        }

        File zip = new File("/tmp/source.zip");
        if(zip.exists()) {
            FileUtils.deleteQuietly(zip);
        }

        File unzipFolder = new File("/tmp/folder");
        if(unzipFolder.exists()) {
            FileUtils.cleanDirectory(unzipFolder);
        }

        File src = new File("/tmp/source");
        if(src.exists()) {
            if(src.isDirectory()) {
                FileUtils.cleanDirectory(src);
            } else {
                FileUtils.deleteQuietly(src);
            }
        }
        src.mkdir();
    }

    @Test
    public void testNullConfig() {
        try {
            new S3DataManager(null, null, null, null, null, null).uploadSourceToS3(listener, testWorkSpace);
        } catch (InvalidInputException e) {
            assertEquals(e.getErrorMessage(), CodeBuilderValidation.invalidSourceUploaderNullS3ClientError);
        } catch(Exception e) {
            fail("Unexpected exception: " + e.getMessage());
        }
    }

    @Test
    public void testUploadSource() throws Exception {
        ArgumentCaptor<PutObjectRequest> savedPutObjectRequest = ArgumentCaptor.forClass(PutObjectRequest.class);
        UploadToS3Output result = createDefaultSource("", "").uploadSourceToS3(listener, testWorkSpace);
        assertEquals(result.getSourceLocation(), s3InputBucketName + "/" + s3InputKeyName);

        verify(s3Client).putObject(savedPutObjectRequest.capture());
        assertEquals(savedPutObjectRequest.getValue().getBucketName(), s3InputBucketName);
        assertEquals(savedPutObjectRequest.getValue().getKey(), s3InputKeyName);
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getSSEAlgorithm(), sseAlgorithm);
    }

    @Test
    public void testUploadSourceSubdir() throws Exception {
        File subdir = new File(mockWorkspaceDir + "/subdir");
        subdir.mkdirs();

        ArgumentCaptor<PutObjectRequest> savedPutObjectRequest = ArgumentCaptor.forClass(PutObjectRequest.class);
        UploadToS3Output result = createDefaultSource("", "subdir").uploadSourceToS3(listener, testWorkSpace);
        assertEquals(result.getSourceLocation(), s3InputBucketName + "/" + s3InputKeyName);

        verify(s3Client).putObject(savedPutObjectRequest.capture());
        assertEquals(savedPutObjectRequest.getValue().getBucketName(), s3InputBucketName);
        assertEquals(savedPutObjectRequest.getValue().getKey(), s3InputKeyName);
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getSSEAlgorithm(), sseAlgorithm);
    }

    @Test
    public void testUploadSourceNonexistentSubdir() {
        try {
            createDefaultSource("", "non-existent-subdir").uploadSourceToS3(listener, testWorkSpace);
        } catch (IOException e) {
            assert(e.getLocalizedMessage().contains("Empty or invalid source directory: " + mockWorkspaceDir + "/non-existent-subdir"));
        } catch(Exception e) {
            fail("Unexpected exception: " + e.getMessage());
        }
    }

    @Test
    public void testUploadSourceSubdirAndLocalSourcePath() {
        try {
            createDefaultSource("source.zip", "subdir").uploadSourceToS3(listener, testWorkSpace);
        } catch (InvalidInputException e) {
            assertEquals(e.getErrorMessage(), CodeBuilderValidation.invalidSourceUploaderConfigError);
        } catch(Exception e) {
            fail("Unexpected exception: " + e.getMessage());
        }
    }

    @Test
    public void testUploadLocalNonexistentSource() {
        try {
            createDefaultSource("non-existent-file", "").uploadSourceToS3(listener, testWorkSpace);
        } catch(IOException e) {
            assert(e.getMessage().contains("non-existent-file' does not exist"));
        } catch(Exception e) {
            fail("Unexpected exception: " + e.getMessage());
        }
    }

    @Test
    public void testUploadLocalSource() throws Exception {
        File file = new File(mockWorkspaceDir + "/source-file");
        FileUtils.write(file, "contents");

        ArgumentCaptor<PutObjectRequest> savedPutObjectRequest = ArgumentCaptor.forClass(PutObjectRequest.class);
        UploadToS3Output result = createDefaultSource(file.getPath(), "").uploadSourceToS3(listener, testWorkSpace);
        assertEquals(result.getSourceLocation(), s3InputBucketName + "/" + s3InputKeyName);

        verify(s3Client).putObject(savedPutObjectRequest.capture());
        assertEquals(savedPutObjectRequest.getValue().getBucketName(), s3InputBucketName);
        assertEquals(savedPutObjectRequest.getValue().getKey(), s3InputKeyName);
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getContentMD5(), S3DataManager.getZipMD5(file));
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getContentLength(), file.length());
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getSSEAlgorithm(), sseAlgorithm);
    }

    @Test
    public void testUploadLocalSourceWithNoSSEAlgorithm() throws Exception {
        File file = new File(mockWorkspaceDir + "/source-file");
        FileUtils.write(file, "contents");

        PutObjectResult mockedResponse = new PutObjectResult();
        mockedResponse.setVersionId("some-version-id");
        when(s3Client.putObject(any(PutObjectRequest.class))).thenReturn(mockedResponse);
        S3DataManager d = new S3DataManager(s3Client, s3InputBucketName, s3InputKeyName, "", file.getPath(), "");

        ArgumentCaptor<PutObjectRequest> savedPutObjectRequest = ArgumentCaptor.forClass(PutObjectRequest.class);
        UploadToS3Output result = d.uploadSourceToS3(listener, testWorkSpace);
        assertEquals(result.getSourceLocation(), s3InputBucketName + "/" + s3InputKeyName);

        verify(s3Client).putObject(savedPutObjectRequest.capture());
        assertEquals(savedPutObjectRequest.getValue().getBucketName(), s3InputBucketName);
        assertEquals(savedPutObjectRequest.getValue().getKey(), s3InputKeyName);
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getContentMD5(), S3DataManager.getZipMD5(file));
        assertEquals(savedPutObjectRequest.getValue().getMetadata().getContentLength(), file.length());
        assertNull(savedPutObjectRequest.getValue().getMetadata().getSSEAlgorithm());
    }


    @Test
    public void testZipSourceEmptyDir() throws Exception {
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        assertFalse(z.isValidZipFile()); //checks that zipfile is empty
    }

    @Test
    public void testZipSourceBuildSpec() throws Exception {
        String buildSpecName = "Buildspec.yml";
        File buildSpec = new File("/tmp/source/" + buildSpecName);
        String buildSpecContents = "yo\n";
        FileUtils.write(buildSpec, buildSpecContents);

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());
        assertTrue(unzipFolder.list().length == 1);
        assertEquals(buildSpecName, unzipFolder.list()[0]);
        File extractedBuildSpec = new File(unzipFolder.getPath() + "/" + buildSpecName);
        assertTrue(FileUtils.readFileToString(extractedBuildSpec).equals(buildSpecContents));
    }

    @Test
    public void testZipSourceOneDirEmpty() throws Exception {
        String buildSpecName = "Buildspec.yml";
        File buildSpec = new File("/tmp/source/" + buildSpecName);
        String buildSpecContents = "yo\n";
        FileUtils.write(buildSpec, buildSpecContents);
        File sourceDir = new File("/tmp/source/src");
        sourceDir.mkdir();

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());
        assertEquals(2, unzipFolder.list().length);
        assertTrue(Arrays.asList(unzipFolder.list()).contains(buildSpecName));
        assertTrue(Arrays.asList(unzipFolder.list()).contains("src"));
        File extractedBuildSpec = new File(unzipFolder.getPath() + "/" + buildSpecName);
        assertEquals(buildSpecContents, FileUtils.readFileToString(extractedBuildSpec));
        File srcDir = new File(unzipFolder.getPath() + "/src");
        assertTrue(srcDir.isDirectory());
        assertEquals(0, srcDir.list().length);
    }

    @Test
    public void testZipSourceOneDir() throws Exception {
        String buildSpecName = "Buildspec.yml";
        File buildSpec = new File("/tmp/source/" + buildSpecName);
        String buildSpecContents = "yo\n";
        FileUtils.write(buildSpec, buildSpecContents);
        File sourceDir = new File("/tmp/source/src");
        sourceDir.mkdir();
        String srcFileName = "/tmp/source/src/file.java";
        File srcFile = new File(srcFileName);
        String srcFileContents = "int i = 1;";
        FileUtils.write(srcFile, srcFileContents);

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());
        String[] fileList = unzipFolder.list();
        assertNotNull(fileList);
        Arrays.sort(fileList);

        assertTrue(fileList.length == 2);
        assertEquals(fileList[0], buildSpecName);
        File extractedBuildSpec = new File(unzipFolder.getPath() + "/" + buildSpecName);
        assertEquals(FileUtils.readFileToString(extractedBuildSpec), buildSpecContents);

        assertEquals(fileList[1], "src");
        File extractedSrcFile = new File(unzipFolder.getPath() + "/src/file.java");
        assertTrue(extractedSrcFile.exists());
        assertEquals(FileUtils.readFileToString(extractedSrcFile), srcFileContents);
    }

    @Test
    public void testZipSourceOneDirMultipleFiles() throws Exception {
        String buildSpecName = "buildspec.yml";
        String rootFileName = "pom.xml";
        String sourceDirName = "src";
        String srcFileName = "file.java";
        String srcFile2Name = "util.java";

        File buildSpec = new File("/tmp/source/" + buildSpecName);
        File rootFile = new File("/tmp/source/" + rootFileName);
        File sourceDir = new File("/tmp/source/" + sourceDirName);
        sourceDir.mkdir();
        File srcFile = new File("/tmp/source/src/" + srcFileName);
        File srcFile2 = new File("/tmp/source/src/" + srcFile2Name);

        String rootFileContents = "<plugin>codebuild</plugin>";
        String buildSpecContents = "Hello!!!!!";
        String srcFileContents = "int i = 1;";
        String srcFile2Contents = "util() { ; }";

        FileUtils.write(buildSpec, buildSpecContents);
        FileUtils.write(rootFile, rootFileContents);
        FileUtils.write(srcFile, srcFileContents);
        FileUtils.write(srcFile2, srcFile2Contents);

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());
        assertTrue(unzipFolder.list().length == 3);
        File srcFolder = new File("/tmp/folder/src/");
        assertTrue(srcFolder.list().length == 2);
        List<String> files = Arrays.asList(unzipFolder.list());
        assertTrue(files.contains(buildSpecName));
        assertTrue(files.contains(rootFileName));
        assertTrue(files.contains(sourceDirName));

        File extractedBuildSpec = new File(unzipFolder.getPath() + "/" + buildSpecName);
        File extractedRootFile = new File(unzipFolder.getPath() + "/" + rootFileName);
        File extractedSrcFile = new File(unzipFolder.getPath() + "/src/" + srcFileName);
        File extractedSrcFile2 = new File(unzipFolder.getPath() + "/src/" + srcFile2Name);
        assertTrue(FileUtils.readFileToString(extractedBuildSpec).equals(buildSpecContents));
        assertTrue(FileUtils.readFileToString(extractedRootFile).equals(rootFileContents));
        assertTrue(FileUtils.readFileToString(extractedSrcFile).equals(srcFileContents));
        assertTrue(FileUtils.readFileToString(extractedSrcFile2).equals(srcFile2Contents));
    }

    @Test
    public void testZipSourceDoubleDirMultipleHugeFiles() throws Exception {
        String buildSpecName = "buildspec.yml";
        String rootFileName = "pom.xml";
        String sourceDirName = "src";
        String nestedDirName = "dir";
        String srcFileName = "file.java";
        String srcFile2Name = "util.java";

        File buildSpec = new File("/tmp/source/" + buildSpecName);
        File rootFile = new File("/tmp/source/" + rootFileName);
        File sourceDir = new File("/tmp/source/" + sourceDirName);
        sourceDir.mkdir();
        File nestedDir = new File("/tmp/source/" + sourceDirName + "/" + nestedDirName);
        nestedDir.mkdir();
        File srcFile = new File("/tmp/source/src/" + srcFileName);
        File srcFile2 = new File("/tmp/source/src/dir/" + srcFile2Name);

        StringBuilder rootFileContents = new StringBuilder();
        for(int i = 0; i < 1000; i++) {
            rootFileContents.append("word\n");
        }
        String buildSpecContents = "Hello!!!!!";
        String srcFileContents = "int i = 1;";
        StringBuilder srcFile2Contents = new StringBuilder();
        for(int i = 0; i < 2000; i++) {
            srcFile2Contents.append("function()");
        }

        FileUtils.write(buildSpec, buildSpecContents);
        FileUtils.write(rootFile, rootFileContents.toString());
        FileUtils.write(srcFile, srcFileContents);
        FileUtils.write(srcFile2, srcFile2Contents.toString());

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());
        assertTrue(unzipFolder.list().length == 3);
        File srcFolder = new File("/tmp/folder/src/");
        assertTrue(srcFolder.list().length == 2);
        File nestedFolder = new  File("/tmp/folder/src/dir/");
        assertTrue(nestedFolder.list().length == 1);
        List<String> files = Arrays.asList(unzipFolder.list());
        assertTrue(files.contains(buildSpecName));
        assertTrue(files.contains(rootFileName));
        assertTrue(files.contains(sourceDirName));
        assertTrue(nestedFolder.list()[0].equals(srcFile2Name));

        File extractedBuildSpec = new File(unzipFolder.getPath() + "/" + buildSpecName);
        File extractedRootFile = new File(unzipFolder.getPath() + "/" + rootFileName);
        File extractedSrcFile = new File(unzipFolder.getPath() + "/src/" + srcFileName);
        File extractedSrcFile2 = new File(unzipFolder.getPath() + "/src/dir/" + srcFile2Name);
        assertTrue(FileUtils.readFileToString(extractedBuildSpec).equals(buildSpecContents));
        assertTrue(FileUtils.readFileToString(extractedRootFile).equals(rootFileContents.toString()));
        assertTrue(FileUtils.readFileToString(extractedSrcFile).equals(srcFileContents));
        assertTrue(FileUtils.readFileToString(extractedSrcFile2).equals(srcFile2Contents.toString()));
    }

    @Test
    public void testZipSourceMultipleNestedDirs() throws Exception {
        String buildSpecName = "buildspec.yml";
        String dir1Name = "dir1";
        String dir2Name = "dir2";
        String dir3Name = "dir3";
        String dir4Name = "dir4";
        String dir5Name = "dir5";
        String nestedFile4Name = "file.txt";
        String nestedFile5Name = "log.txt";

        File buildSpec = new File("/tmp/source/" + buildSpecName);
        File dir1 = new File("/tmp/source/" + dir1Name); dir1.mkdir();
        File dir2 = new File("/tmp/source/" + dir1Name + "/" + dir2Name); dir2.mkdir();
        File dir3 = new File("/tmp/source/" + dir1Name + "/" + dir2Name + "/" + dir3Name); dir3.mkdir();
        File dir4 = new File("/tmp/source/dir1/dir2/dir3/" + dir4Name); dir4.mkdir();
        File dir5 = new File("/tmp/source/dir1/dir2/dir3/" + dir5Name); dir5.mkdir();
        File file4 = new File("/tmp/source/dir1/dir2/dir3/dir4/" + nestedFile4Name);
        File file5 = new File("/tmp/source/dir1/dir2/dir3/dir5/" + nestedFile5Name);

        String buildSpecContents = "Hello!!!!!";
        String nestedFile4Contents = "words";
        String nestedFile5Contents = "logfile";
        FileUtils.write(buildSpec, buildSpecContents);
        FileUtils.write(file4, nestedFile4Contents);
        FileUtils.write(file5, nestedFile5Contents);

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());

        unzipFolder = new File("/tmp/folder/" + dir1Name);
        assertTrue(unzipFolder.list().length == 1);
        assertTrue(unzipFolder.list()[0].equals(dir2Name));

        unzipFolder = new File("/tmp/folder/" + dir1Name + "/" + dir2Name);
        assertTrue(unzipFolder.list().length == 1);
        assertTrue(unzipFolder.list()[0].equals(dir3Name));

        unzipFolder = new File("/tmp/folder/dir1/dir2/dir3/");
        assertTrue(unzipFolder.list().length == 2);

        unzipFolder = new File("/tmp/folder/dir1/dir2/dir3/dir4/");
        assertTrue(unzipFolder.list().length == 1);
        assertTrue(unzipFolder.list()[0].equals(nestedFile4Name));

        unzipFolder = new File("/tmp/folder/dir1/dir2/dir3/dir5/");
        assertTrue(unzipFolder.list().length == 1);
        assertTrue(unzipFolder.list()[0].equals(nestedFile5Name));
    }

    @Test
    public void testZipSourceHugeFile() throws Exception {
        String buildSpecName = "buildspec.yml";
        File buildSpec = new File("/tmp/source/" + buildSpecName);

        StringBuilder contents = new StringBuilder();

        for(int i = 0; i < 50000; i++) {
            contents.append("123234345456567678456234");
        }

        FileUtils.write(buildSpec, contents.toString());

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream("/tmp/source.zip"));
        ZipSourceCallable.zipSource(testZipSourceWorkspace, "/tmp/source/", out, "/tmp/source/");
        out.close();

        File zip = new File("/tmp/source.zip");
        assertTrue(zip.exists());

        File unzipFolder = new File("/tmp/folder/");
        unzipFolder.mkdir();
        ZipFile z = new ZipFile(zip.getPath());
        z.extractAll(unzipFolder.getPath());
        assertTrue(unzipFolder.list().length == 1);
        assertTrue(unzipFolder.list()[0].equals(buildSpecName));

        File extractedBuildSpec = new File(unzipFolder.getPath() + "/" + buildSpecName);
        assertTrue(FileUtils.readFileToString(extractedBuildSpec).equals(contents.toString()));
    }

    @Test
    public void testTrimPrefixBaseWithTrailingSlash() {
        String prefixWithSlash = FilenameUtils.separatorsToSystem("/tmp/dir/");  // "/tmp/dir/" in Linux, "\tmp\dir\" in Windows.
        String path = FilenameUtils.separatorsToSystem("/tmp/dir/folder/file.txt");

        assertEquals(FilenameUtils.separatorsToSystem("folder/file.txt"), ZipSourceCallable.trimPrefix(path, prefixWithSlash));
    }

    @Test
    public void testGetRelativePathStringBaseDirWithoutTrailingSlash() {
        String prefixNoSlash = FilenameUtils.separatorsToSystem("/tmp/dir"); // "/tmp/dir" in Linux, "\tmp\dir" in Windows.
        String path = FilenameUtils.separatorsToSystem("/tmp/dir/folder/file.txt");

        assertEquals(FilenameUtils.separatorsToSystem("folder/file.txt"), ZipSourceCallable.trimPrefix(path, prefixNoSlash));
    }
}