// Copyright © 2017 Asteris, LLC
//
// 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 unarchive

import (
	"archive/zip"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/asteris-llc/converge/helpers/fakerenderer"
	"github.com/asteris-llc/converge/resource"
	"github.com/asteris-llc/converge/resource/file/fetch"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

// TestUnarchiveInterface tests that Unarchive is properly implemented
func TestUnarchiveInterface(t *testing.T) {
	t.Parallel()

	assert.Implements(t, (*resource.Task)(nil), new(Unarchive))
}

// TestCheck tests the cases Check handles
func TestCheck(t *testing.T) {
	t.Parallel()

	srcFile, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(t, err)
	defer os.Remove(srcFile.Name())

	destInvalid, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(t, err)
	defer os.Remove(destInvalid.Name())

	srcDir, err := ioutil.TempDir("", "unarchive_srcDir")
	require.NoError(t, err)
	defer os.RemoveAll(srcDir)

	destDir, err := ioutil.TempDir("", "unarchive_destDir")
	require.NoError(t, err)
	defer os.RemoveAll(destDir)

	tmpFetchDir, err := ioutil.TempDir("", "tmpFetchDir")
	require.NoError(t, err)
	defer os.RemoveAll(tmpFetchDir)

	t.Run("error", func(t *testing.T) {
		t.Run("diff error", func(t *testing.T) {
			u := &Unarchive{
				Source:      srcFile.Name(),
				Destination: destInvalid.Name(),
			}

			status, err := u.Check(context.Background(), fakerenderer.New())

			assert.EqualError(t, err, fmt.Sprintf("invalid destination %q, must be directory", u.Destination))
			assert.Equal(t, resource.StatusCantChange, status.StatusCode())
			assert.True(t, status.HasChanges())
		})

		t.Run("fetch error", func(t *testing.T) {
			u := &Unarchive{
				Source:      srcFile.Name(),
				Destination: destDir,
				HashType:    string(HashMD5),
				Hash:        "notarealhashbutstringnonetheless",
				Force:       false,
				fetchLoc:    destInvalid.Name(),
			}

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}

			status, err := u.Check(context.Background(), fakerenderer.New())

			assert.EqualError(t, err, fmt.Sprintf("cannot attempt unarchive: fetch error: invalid destination %q for unarchiving, must be directory", u.fetch.Destination))
			assert.Equal(t, resource.StatusCantChange, status.StatusCode())
			assert.True(t, status.HasChanges())
		})
	})

	t.Run("unarchive", func(t *testing.T) {
		u := &Unarchive{
			Source:      srcFile.Name(),
			Destination: "/tmp",
		}

		status, err := u.Check(context.Background(), fakerenderer.New())

		assert.NoError(t, err)
		assert.Equal(t, fmt.Sprintf("fetch and unarchive %q", u.Source), status.Messages()[0])
		assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
		assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
		assert.Equal(t, resource.StatusWillChange, status.StatusCode())
		assert.True(t, status.HasChanges())
	})

	t.Run("context", func(t *testing.T) {
		u := &Unarchive{}
		ctx, cancel := context.WithCancel(context.Background())
		cancel()

		status, err := u.Check(ctx, fakerenderer.New())

		assert.EqualError(t, err, "context canceled")
		assert.Nil(t, status)
	})
}

// TestApply tests the cases Apply handles
func TestApply(t *testing.T) {
	t.Parallel()

	srcDir, err := ioutil.TempDir("", "unarchive_srcDir")
	require.NoError(t, err)
	defer os.RemoveAll(srcDir)

	srcFile, err := ioutil.TempFile("", "unarchive_file.txt")
	require.NoError(t, err)
	defer os.Remove(srcFile.Name())

	destDir, err := ioutil.TempDir("", "unarchive_destDir")
	require.NoError(t, err)
	defer os.RemoveAll(destDir)

	tmpFetchDir, err := ioutil.TempDir("", "tmpFetchDir")
	require.NoError(t, err)
	defer os.RemoveAll(tmpFetchDir)

	t.Run("error", func(t *testing.T) {
		t.Run("diff error", func(t *testing.T) {
			u := &Unarchive{}

			status, err := u.Apply(context.Background())

			assert.EqualError(t, err, fmt.Sprintf("cannot unarchive: stat %s: no such file or directory", u.Source))
			assert.Equal(t, resource.StatusCantChange, status.StatusCode())
			assert.True(t, status.HasChanges())
		})

		t.Run("fetch error", func(t *testing.T) {
			u := &Unarchive{
				Source:      srcFile.Name(),
				Destination: destDir,
				HashType:    string(HashMD5),
				Hash:        "notarealhashbutstringnonetheless",
				Force:       false,
				fetchLoc:    tmpFetchDir,
			}

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}

			status, err := u.Apply(context.Background())

			assert.EqualError(t, err, "failed to fetch: invalid checksum: encoding/hex: invalid byte: U+006E 'n'")
			assert.Equal(t, resource.StatusFatal, status.StatusCode())
			assert.False(t, status.HasChanges())
		})

		t.Run("evaluateDuplicates error", func(t *testing.T) {
			destDir, err := ioutil.TempDir("", "destDir_unarchive")
			require.NoError(t, err)
			defer os.RemoveAll(destDir)

			fileBDest, err := os.Create(destDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBDest.Name())

			fileBFetch, err := os.Create("/tmp/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBFetch.Name())

			_, err = fileBFetch.Write([]byte{1})
			require.NoError(t, err)

			// zip fetchDir to use as our unarchive source
			zipFile := "/tmp/unarchive_test_zip.zip"
			err = zipFiles(fileBFetch.Name(), zipFile)
			require.NoError(t, err)

			u := &Unarchive{
				Source:      zipFile,
				Destination: destDir,
				fetchLoc:    tmpFetchDir,
			}

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}
			defer os.Remove(u.Source)
			defer os.RemoveAll(u.Destination)

			checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum("/tmp/fileB.txt")
			require.NoError(t, err)
			require.NotEqual(t, checksumDest, checksumFetch)

			status, err := u.Apply(context.Background())

			assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/fileB.txt\" exists at %q: checksum mismatch", u.Destination))
			assert.Equal(t, "use the \"force\" option to replace all files with checksum mismatch", status.Messages()[0])
			assert.Equal(t, resource.StatusFatal, status.StatusCode())
			assert.False(t, status.HasChanges())
		})
	})

	t.Run("success", func(t *testing.T) {
		t.Run("empty dest", func(t *testing.T) {
			destDir, err := ioutil.TempDir("", "destDir_unarchive")
			require.NoError(t, err)
			defer os.RemoveAll(destDir)

			fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
			require.NoError(t, err)
			defer os.RemoveAll(fetchDir)

			fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBFetch.Name())

			// zip fetchDir to use as our unarchive source
			zipFile := "/tmp/unarchive_test_zip.zip"
			err = zipFiles(fetchDir, zipFile)
			require.NoError(t, err)

			u := &Unarchive{
				Source:      zipFile,
				Destination: destDir,
				fetchLoc:    tmpFetchDir,
			}

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}
			defer os.Remove(u.Source)
			defer os.RemoveAll(u.Destination)

			status, err := u.Apply(context.Background())

			assert.NoError(t, err)
			assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
			assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
			assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
		})

		t.Run("checksum match", func(t *testing.T) {
			destDir, err := ioutil.TempDir("", "destDir_unarchive")
			require.NoError(t, err)
			defer os.RemoveAll(destDir)

			fileBDest, err := os.Create(destDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBDest.Name())

			fileBFetch, err := os.Create("/tmp/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBFetch.Name())

			// zip fetchDir to use as our unarchive source
			zipFile := "/tmp/unarchive_test_zip.zip"
			err = zipFiles(fileBFetch.Name(), zipFile)
			require.NoError(t, err)

			u := &Unarchive{
				Source:      zipFile,
				Destination: destDir,
				fetchLoc:    tmpFetchDir,
			}

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}
			defer os.Remove(u.Source)
			defer os.RemoveAll(u.Destination)

			checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum("/tmp/fileB.txt")
			require.NoError(t, err)
			require.Equal(t, checksumDest, checksumFetch)

			status, err := u.Apply(context.Background())

			assert.NoError(t, err)
			assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
			assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
			assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
		})

		t.Run("checksum mismatch", func(t *testing.T) {
			destDir, err := ioutil.TempDir("", "destDir_unarchive")
			require.NoError(t, err)
			defer os.RemoveAll(destDir)

			fileBDest, err := os.Create(destDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBDest.Name())

			fileBFetch, err := os.Create("/tmp/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBFetch.Name())

			_, err = fileBFetch.Write([]byte{1})
			require.NoError(t, err)

			// zip fetchDir to use as our unarchive source
			zipFile := "/tmp/unarchive_test_zip.zip"
			err = zipFiles(fileBFetch.Name(), zipFile)
			require.NoError(t, err)

			u := &Unarchive{
				Source:      zipFile,
				Destination: destDir,
				Force:       true,
				fetchLoc:    tmpFetchDir,
			}

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}
			defer os.Remove(u.Source)
			defer os.RemoveAll(u.Destination)

			checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum("/tmp/fileB.txt")
			require.NoError(t, err)
			require.NotEqual(t, checksumDest, checksumFetch)

			status, testErr := u.Apply(context.Background())

			checksumDest, err = u.getChecksum(destDir + "/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err = u.getChecksum("/tmp/fileB.txt")
			require.NoError(t, err)

			assert.NoError(t, testErr)
			assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
			assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
			assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
			assert.Equal(t, checksumDest, checksumFetch)
		})

		t.Run("fetch with checksum", func(t *testing.T) {
			destDir, err := ioutil.TempDir("", "destDir_unarchive")
			require.NoError(t, err)
			defer os.RemoveAll(destDir)

			fileBDest, err := os.Create(destDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBDest.Name())

			fileBFetch, err := os.Create("/tmp/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBFetch.Name())

			// zip fetchDir to use as our unarchive source
			zipFile := "/tmp/unarchive_test_zip.zip"
			err = zipFiles(fileBFetch.Name(), zipFile)
			require.NoError(t, err)

			u := &Unarchive{
				Source:      zipFile,
				Destination: destDir,
				fetchLoc:    tmpFetchDir,
			}

			srcChecksum, err := u.getChecksum(zipFile)
			require.NoError(t, err)

			u.HashType = "sha256"
			u.Hash = srcChecksum

			u.fetch = fetch.Fetch{
				Source:      u.Source,
				Destination: u.fetchLoc,
				HashType:    u.HashType,
				Hash:        u.Hash,
				Unarchive:   true,
			}
			defer os.Remove(u.Source)
			defer os.RemoveAll(u.Destination)

			status, err := u.Apply(context.Background())

			assert.NoError(t, err)
			assert.Equal(t, fmt.Sprintf("completed fetch and unarchive %q", u.Source), status.Messages()[0])
			assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
			assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
		})
	})

	t.Run("context", func(t *testing.T) {
		u := &Unarchive{}
		ctx, cancel := context.WithCancel(context.Background())
		cancel()

		status, err := u.Apply(ctx)

		assert.EqualError(t, err, "context canceled")
		assert.Nil(t, status)
	})
}

// TestDiff tests diff for Unarchive
func TestDiff(t *testing.T) {
	t.Parallel()

	src, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(t, err)
	defer os.Remove(src.Name())

	destInvalid, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(t, err)
	defer os.Remove(destInvalid.Name())

	t.Run("source does not exist", func(t *testing.T) {
		u := &Unarchive{
			Source:      "",
			Destination: "/tmp",
		}
		status := resource.NewStatus()

		err := u.diff(status)

		assert.EqualError(t, err, "cannot unarchive: stat : no such file or directory")
		assert.Equal(t, resource.StatusCantChange, status.StatusCode())
		assert.True(t, status.HasChanges())
	})

	t.Run("destination is not directory", func(t *testing.T) {
		u := &Unarchive{
			Source:      src.Name(),
			Destination: destInvalid.Name(),
		}
		status := resource.NewStatus()

		err := u.diff(status)

		assert.EqualError(t, err, fmt.Sprintf("invalid destination \"%s\", must be directory", u.Destination))
		assert.Equal(t, resource.StatusCantChange, status.StatusCode())
		assert.True(t, status.HasChanges())
	})

	t.Run("destination does not exist", func(t *testing.T) {
		u := &Unarchive{
			Source:      src.Name(),
			Destination: "",
		}
		status := resource.NewStatus()

		err := u.diff(status)

		assert.EqualError(t, err, fmt.Sprintf("destination \"%s\" does not exist", u.Destination))
		assert.Equal(t, resource.StatusCantChange, status.StatusCode())
		assert.True(t, status.HasChanges())
	})

	t.Run("unarchive", func(t *testing.T) {
		u := &Unarchive{
			Source:      src.Name(),
			Destination: "/tmp",
		}
		status := resource.NewStatus()

		err := u.diff(status)

		assert.NoError(t, err)
		assert.Equal(t, u.Source, status.Diffs()["unarchive"].Original())
		assert.Equal(t, u.Destination, status.Diffs()["unarchive"].Current())
		assert.Equal(t, resource.StatusWillChange, status.StatusCode())
		assert.True(t, status.HasChanges())
	})
}

// BenchmarkDiff benchmarks diff for Unarchive
func BenchmarkDiff(b *testing.B) {
	src, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(b, err)
	defer os.Remove(src.Name())

	destInvalid, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(b, err)
	defer os.Remove(destInvalid.Name())

	status := resource.NewStatus()

	benchmarks := []struct {
		name string
		Unarchive
	}{
		{"source does not exist",
			Unarchive{
				Source:      "",
				Destination: "/tmp",
			},
		},
		{"destination is not directory",
			Unarchive{
				Source:      src.Name(),
				Destination: destInvalid.Name(),
			},
		},
		{"destination does not exist",
			Unarchive{
				Source:      src.Name(),
				Destination: "",
			},
		},
		{"unarchive",
			Unarchive{
				Source:      src.Name(),
				Destination: "/tmp",
			},
		},
	}

	for _, bm := range benchmarks {
		b.Run(bm.name, func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				bm.Unarchive.diff(status)
			}
		})
	}
}

// TestSetDirsAndContents tests setDirsAndContents for Unarchive
func TestSetDirsAndContents(t *testing.T) {
	t.Parallel()

	srcFile, err := ioutil.TempFile("", "unarchive_test.zip")
	require.NoError(t, err)
	defer os.Remove(srcFile.Name())

	t.Run("dest not exist", func(t *testing.T) {
		notExistDir := "/tmp/unarchive_test12345678"
		_, err := os.Stat(notExistDir)
		require.True(t, os.IsNotExist(err))

		u := &Unarchive{
			Source:      srcFile.Name(),
			Destination: notExistDir,
		}
		defer os.RemoveAll(u.Destination)

		evalDups, err := u.setDirsAndContents()

		_, exists := os.Stat(notExistDir)

		assert.EqualError(t, err, fmt.Sprintf("open %s: no such file or directory", u.Destination))
		assert.False(t, evalDups)
		assert.True(t, os.IsNotExist(exists))
	})

	t.Run("empty dest", func(t *testing.T) {
		emptyDir, err := ioutil.TempDir("", "unarchive_empty")
		require.NoError(t, err)
		defer os.RemoveAll(emptyDir)

		u := &Unarchive{
			Source:      srcFile.Name(),
			Destination: emptyDir,
		}
		defer os.RemoveAll(u.Destination)

		err = setupSetDirsAndContents(u, false)
		require.NoError(t, err)
		defer os.RemoveAll(u.fetchLoc)

		expF := [2]string{u.fetchLoc, "fetchFileA.txt"}
		expD := [1]string{emptyDir}

		evalDups, err := u.setDirsAndContents()

		assert.NoError(t, err)
		assert.False(t, evalDups)
		assert.Equal(t, 2, len(u.fetchContents))
		assert.Equal(t, 1, len(u.destContents))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[0]) || strings.Contains(u.fetchContents[1], expF[0]))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[1]) || strings.Contains(u.fetchContents[1], expF[1]))
		assert.True(t, strings.Contains(u.destContents[0], expD[0]))
	})

	t.Run("fetch dir", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "unarchive_dir")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		nestedDir, err := ioutil.TempDir(destDir, "unarchive_nest_dir")
		require.NoError(t, err)
		defer os.RemoveAll(nestedDir)

		nestedFile, err := ioutil.TempFile(destDir, "unarchive_nest_file")
		require.NoError(t, err)
		defer os.Remove(nestedFile.Name())

		u := &Unarchive{
			Source:      srcFile.Name(),
			Destination: destDir,
		}
		defer os.RemoveAll(u.Destination)

		err = setupSetDirsAndContents(u, false)
		require.NoError(t, err)
		defer os.RemoveAll(u.fetchLoc)

		expF := [2]string{u.fetchLoc, "fetchFileA.txt"}
		expD := [3]string{destDir, nestedDir, nestedFile.Name()}

		evalDups, err := u.setDirsAndContents()

		assert.NoError(t, err)
		assert.True(t, evalDups)
		assert.Equal(t, 2, len(u.fetchContents))
		assert.Equal(t, 3, len(u.destContents))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[0]) || strings.Contains(u.fetchContents[1], expF[0]))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[1]) || strings.Contains(u.fetchContents[1], expF[1]))
		assert.True(t, strings.Contains(u.destContents[0], expD[0]) || strings.Contains(u.destContents[1], expD[0]) || strings.Contains(u.destContents[2], expD[0]))
		assert.True(t, strings.Contains(u.destContents[0], expD[1]) || strings.Contains(u.destContents[1], expD[1]) || strings.Contains(u.destContents[2], expD[1]))
		assert.True(t, strings.Contains(u.destContents[0], expD[2]) || strings.Contains(u.destContents[1], expD[2]) || strings.Contains(u.destContents[2], expD[2]))
	})

	t.Run("nested fetch dir", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "unarchive_dir")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		nestedDir, err := ioutil.TempDir(destDir, "unarchive_nest_dir")
		require.NoError(t, err)
		defer os.RemoveAll(nestedDir)

		nestedFile, err := ioutil.TempFile(destDir, "unarchive_nest_file")
		require.NoError(t, err)
		defer os.Remove(nestedFile.Name())

		u := &Unarchive{
			Source:      srcFile.Name(),
			Destination: destDir,
		}
		defer os.RemoveAll(u.Destination)

		err = setupSetDirsAndContents(u, true)
		require.NoError(t, err)
		defer os.RemoveAll(u.fetchLoc)

		expF := [4]string{u.fetchLoc, "/unarchive_fetch_nest", "fetchFileB.txt", "fetchFileC.txt"}
		expD := [3]string{destDir, nestedDir, nestedFile.Name()}

		evalDups, err := u.setDirsAndContents()

		assert.NoError(t, err)
		assert.True(t, evalDups)
		assert.Equal(t, 4, len(u.fetchContents))
		assert.Equal(t, 3, len(u.destContents))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[0]) || strings.Contains(u.fetchContents[1], expF[0]) || strings.Contains(u.fetchContents[2], expF[0]) || strings.Contains(u.fetchContents[3], expF[0]))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[1]) || strings.Contains(u.fetchContents[1], expF[1]) || strings.Contains(u.fetchContents[2], expF[1]) || strings.Contains(u.fetchContents[3], expF[1]))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[2]) || strings.Contains(u.fetchContents[1], expF[2]) || strings.Contains(u.fetchContents[2], expF[2]) || strings.Contains(u.fetchContents[3], expF[2]))
		assert.True(t, strings.Contains(u.fetchContents[0], expF[3]) || strings.Contains(u.fetchContents[1], expF[3]) || strings.Contains(u.fetchContents[2], expF[3]) || strings.Contains(u.fetchContents[3], expF[3]))
		assert.True(t, strings.Contains(u.destContents[0], expD[0]) || strings.Contains(u.destContents[1], expD[0]) || strings.Contains(u.destContents[2], expD[0]))
		assert.True(t, strings.Contains(u.destContents[0], expD[1]) || strings.Contains(u.destContents[1], expD[1]) || strings.Contains(u.destContents[2], expD[1]))
		assert.True(t, strings.Contains(u.destContents[0], expD[2]) || strings.Contains(u.destContents[1], expD[2]) || strings.Contains(u.destContents[2], expD[2]))
	})
}

// TestEvaluateDuplicates tests evaluateDuplicates for Unarchive
func TestEvaluateDuplicates(t *testing.T) {
	t.Parallel()

	t.Run("no duplicates", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "destDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		fileA, err := ioutil.TempFile(destDir, "fileA.txt")
		require.NoError(t, err)
		defer os.Remove(fileA.Name())

		fileB, err := ioutil.TempFile(destDir, "fileB.txt")
		require.NoError(t, err)
		defer os.Remove(fileB.Name())

		ddInfo, err := os.Open(destDir)
		require.NoError(t, err)
		defer ddInfo.Close()

		fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(fetchDir)

		fileC, err := ioutil.TempFile(fetchDir, "fileC.txt")
		require.NoError(t, err)
		defer os.Remove(fileC.Name())

		fileD, err := ioutil.TempFile(fetchDir, "fileD.txt")
		require.NoError(t, err)
		defer os.Remove(fileD.Name())

		fdInfo, err := os.Open(fetchDir)
		require.NoError(t, err)
		defer fdInfo.Close()

		u := &Unarchive{
			destDir:  ddInfo,
			fetchDir: fdInfo,
		}

		err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
			u.destContents = append(u.destContents, path)
			return nil
		})
		require.NoError(t, err)

		err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
			u.fetchContents = append(u.fetchContents, path)
			return nil
		})
		require.NoError(t, err)

		err = u.evaluateDuplicates()

		assert.NoError(t, err)
	})

	t.Run("duplicates", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "destDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		fileADest, err := os.Create(destDir + "/fileA.txt")
		require.NoError(t, err)
		defer os.Remove(fileADest.Name())

		ddInfo, err := os.Open(destDir)
		require.NoError(t, err)
		defer ddInfo.Close()

		fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(fetchDir)

		fileAFetch, err := os.Create(fetchDir + "/fileA.txt")
		require.NoError(t, err)
		defer os.Remove(fileAFetch.Name())

		fdInfo, err := os.Open(fetchDir)
		require.NoError(t, err)
		defer fdInfo.Close()

		t.Run("checksum match", func(t *testing.T) {
			u := &Unarchive{
				destDir:  ddInfo,
				fetchDir: fdInfo,
			}

			checksumDest, err := u.getChecksum(destDir + "/fileA.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum(fetchDir + "/fileA.txt")
			require.NoError(t, err)
			require.Equal(t, checksumDest, checksumFetch)

			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.destContents = append(u.destContents, path)
				return nil
			})
			require.NoError(t, err)

			err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.fetchContents = append(u.fetchContents, path)
				return nil
			})
			require.NoError(t, err)

			err = u.evaluateDuplicates()

			assert.NoError(t, err)
		})

		t.Run("checksum mismatch", func(t *testing.T) {
			t.Run("different size", func(t *testing.T) {
				fileBDest, err := os.Create(destDir + "/fileB.txt")
				require.NoError(t, err)
				defer os.Remove(fileBDest.Name())

				fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
				require.NoError(t, err)
				defer os.Remove(fileBFetch.Name())

				_, err = fileBFetch.Write([]byte{1})
				require.NoError(t, err)

				dStat, _ := os.Stat(destDir + "/fileB.txt")
				fStat, _ := os.Stat(fetchDir + "/fileB.txt")
				require.NotEqual(t, dStat.Size(), fStat.Size())

				u := &Unarchive{
					Destination: destDir,
					destDir:     ddInfo,
					fetchDir:    fdInfo,
				}

				checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
				require.NoError(t, err)
				checksumFetch, err := u.getChecksum(fetchDir + "/fileB.txt")
				require.NoError(t, err)
				require.NotEqual(t, checksumDest, checksumFetch)

				err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
					u.destContents = append(u.destContents, path)
					return nil
				})
				require.NoError(t, err)

				err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
					u.fetchContents = append(u.fetchContents, path)
					return nil
				})
				require.NoError(t, err)

				err = u.evaluateDuplicates()

				assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/fileB.txt\" exists at %q: checksum mismatch", u.Destination))
			})

			t.Run("same size", func(t *testing.T) {
				fileBDest, err := os.Create(destDir + "/fileB.txt")
				require.NoError(t, err)
				defer os.Remove(fileBDest.Name())

				_, err = fileBDest.WriteString("a")
				require.NoError(t, err)

				fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
				require.NoError(t, err)
				defer os.Remove(fileBFetch.Name())

				_, err = fileBFetch.WriteString("b")
				require.NoError(t, err)

				dStat, _ := os.Stat(destDir + "/fileB.txt")
				fStat, _ := os.Stat(fetchDir + "/fileB.txt")
				require.Equal(t, dStat.Size(), fStat.Size())

				u := &Unarchive{
					Destination: destDir,
					destDir:     ddInfo,
					fetchDir:    fdInfo,
				}

				checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
				require.NoError(t, err)
				checksumFetch, err := u.getChecksum(fetchDir + "/fileB.txt")
				require.NoError(t, err)
				require.NotEqual(t, checksumDest, checksumFetch)

				err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
					u.destContents = append(u.destContents, path)
					return nil
				})
				require.NoError(t, err)

				err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
					u.fetchContents = append(u.fetchContents, path)
					return nil
				})
				require.NoError(t, err)

				err = u.evaluateDuplicates()

				assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/fileB.txt\" exists at %q: checksum mismatch", u.Destination))
			})
		})
	})

	t.Run("recurse", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "destDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		stat, err := os.Stat(destDir)
		require.NoError(t, err)

		err = os.Mkdir(destDir+"/dirA", stat.Mode().Perm())
		require.NoError(t, err)

		err = os.Mkdir(destDir+"/dirA/dirB", stat.Mode().Perm())
		require.NoError(t, err)

		fileBDest, err := os.Create(destDir + "/dirA/dirB/fileB.txt")
		require.NoError(t, err)
		defer os.Remove(fileBDest.Name())

		ddInfo, err := os.Open(destDir)
		require.NoError(t, err)
		defer ddInfo.Close()

		fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(fetchDir)

		stat, err = os.Stat(fetchDir)
		require.NoError(t, err)

		err = os.Mkdir(fetchDir+"/dirA", stat.Mode().Perm())
		require.NoError(t, err)

		err = os.Mkdir(fetchDir+"/dirA/dirB", stat.Mode().Perm())
		require.NoError(t, err)

		fileBFetch, err := os.Create(fetchDir + "/dirA/dirB/fileB.txt")
		require.NoError(t, err)
		defer os.Remove(fileBFetch.Name())

		fdInfo, err := os.Open(fetchDir)
		require.NoError(t, err)
		defer fdInfo.Close()

		t.Run("checksum match", func(t *testing.T) {
			u := &Unarchive{
				Destination: destDir,
				destDir:     ddInfo,
				fetchDir:    fdInfo,
			}

			checksumDest, err := u.getChecksum(destDir + "/dirA/dirB/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum(fetchDir + "/dirA/dirB/fileB.txt")
			require.NoError(t, err)
			require.Equal(t, checksumDest, checksumFetch)

			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.destContents = append(u.destContents, path)
				return nil
			})
			require.NoError(t, err)

			err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.fetchContents = append(u.fetchContents, path)
				return nil
			})
			require.NoError(t, err)

			err = u.evaluateDuplicates()

			assert.NoError(t, err)
		})

		t.Run("checksum mismatch", func(t *testing.T) {
			fileCDest, err := os.Create(destDir + "/dirA/dirB/fileC.txt")
			require.NoError(t, err)
			defer os.Remove(fileCDest.Name())

			fileCFetch, err := os.Create(fetchDir + "/dirA/dirB/fileC.txt")
			require.NoError(t, err)
			defer os.Remove(fileCFetch.Name())

			_, err = fileCFetch.Write([]byte{2})
			require.NoError(t, err)

			u := &Unarchive{
				Destination: destDir,
				destDir:     ddInfo,
				fetchDir:    fdInfo,
			}

			checksumDest, err := u.getChecksum(destDir + "/dirA/dirB/fileC.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum(fetchDir + "/dirA/dirB/fileC.txt")
			require.NoError(t, err)
			require.NotEqual(t, checksumDest, checksumFetch)

			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.destContents = append(u.destContents, path)
				return nil
			})
			require.NoError(t, err)

			err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.fetchContents = append(u.fetchContents, path)
				return nil
			})
			require.NoError(t, err)

			err = u.evaluateDuplicates()

			assert.EqualError(t, err, fmt.Sprintf("will not replace, \"/dirA/dirB/fileC.txt\" exists at %q: checksum mismatch", u.Destination))
		})
	})
}

// TestCopyToFinalDest tests copyToFinalDest for Unarchive
func TestCopyToFinalDest(t *testing.T) {
	t.Parallel()

	t.Run("no duplicates", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "destDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		fileA, err := ioutil.TempFile(destDir, "fileA.txt")
		require.NoError(t, err)
		defer os.Remove(fileA.Name())

		fileB, err := ioutil.TempFile(destDir, "fileB.txt")
		require.NoError(t, err)
		defer os.Remove(fileB.Name())

		ddInfo, err := os.Open(destDir)
		require.NoError(t, err)
		defer ddInfo.Close()

		fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(fetchDir)

		fileC, err := ioutil.TempFile(fetchDir, "fileC.txt")
		require.NoError(t, err)
		defer os.Remove(fileC.Name())

		fileD, err := ioutil.TempFile(fetchDir, "fileD.txt")
		require.NoError(t, err)
		defer os.Remove(fileD.Name())

		fdInfo, err := os.Open(fetchDir)
		require.NoError(t, err)
		defer fdInfo.Close()

		u := &Unarchive{
			destDir:  ddInfo,
			fetchDir: fdInfo,
		}

		err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
			u.destContents = append(u.destContents, path)
			return nil
		})
		require.NoError(t, err)

		err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
			u.fetchContents = append(u.fetchContents, path)
			return nil
		})
		require.NoError(t, err)

		exp := []string{destDir, "fileA.txt", "fileB.txt", "fileC.txt", "fileD.txt"}

		testErr := u.copyToFinalDest()

		act := []string{}
		err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
			act = append(act, path)
			return nil
		})
		require.NoError(t, err)

		assert.NoError(t, testErr)
		assert.Equal(t, 5, len(act))
		assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]) || strings.Contains(act[2], exp[0]) || strings.Contains(act[3], exp[0]) || strings.Contains(act[4], exp[0]))
		assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]) || strings.Contains(act[2], exp[1]) || strings.Contains(act[3], exp[1]) || strings.Contains(act[4], exp[1]))
		assert.True(t, strings.Contains(act[0], exp[2]) || strings.Contains(act[1], exp[2]) || strings.Contains(act[2], exp[2]) || strings.Contains(act[3], exp[2]) || strings.Contains(act[4], exp[2]))
		assert.True(t, strings.Contains(act[0], exp[3]) || strings.Contains(act[1], exp[3]) || strings.Contains(act[2], exp[3]) || strings.Contains(act[3], exp[3]) || strings.Contains(act[4], exp[3]))
		assert.True(t, strings.Contains(act[0], exp[4]) || strings.Contains(act[1], exp[4]) || strings.Contains(act[2], exp[4]) || strings.Contains(act[3], exp[4]) || strings.Contains(act[4], exp[4]))
	})

	t.Run("duplicates", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "destDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		fileADest, err := os.Create(destDir + "/fileA.txt")
		require.NoError(t, err)
		defer os.Remove(fileADest.Name())

		ddInfo, err := os.Open(destDir)
		require.NoError(t, err)
		defer ddInfo.Close()

		fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(fetchDir)

		fileAFetch, err := os.Create(fetchDir + "/fileA.txt")
		require.NoError(t, err)
		defer os.Remove(fileAFetch.Name())

		fdInfo, err := os.Open(fetchDir)
		require.NoError(t, err)
		defer fdInfo.Close()

		t.Run("checksum match", func(t *testing.T) {
			u := &Unarchive{
				destDir:  ddInfo,
				fetchDir: fdInfo,
			}

			checksumDest, err := u.getChecksum(destDir + "/fileA.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum(fetchDir + "/fileA.txt")
			require.NoError(t, err)
			require.Equal(t, checksumDest, checksumFetch)

			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.destContents = append(u.destContents, path)
				return nil
			})
			require.NoError(t, err)

			err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.fetchContents = append(u.fetchContents, path)
				return nil
			})
			require.NoError(t, err)

			exp := []string{destDir, "fileA.txt"}

			testErr := u.copyToFinalDest()

			act := []string{}
			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				act = append(act, path)
				return nil
			})
			require.NoError(t, err)

			assert.NoError(t, testErr)
			assert.Equal(t, 2, len(act))
			assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]))
			assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]))
		})

		t.Run("checksum mismatch", func(t *testing.T) {
			fileBDest, err := os.Create(destDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBDest.Name())

			fileBFetch, err := os.Create(fetchDir + "/fileB.txt")
			require.NoError(t, err)
			defer os.Remove(fileBFetch.Name())

			_, err = fileBFetch.Write([]byte{1})
			require.NoError(t, err)

			u := &Unarchive{
				Destination: destDir,
				destDir:     ddInfo,
				fetchDir:    fdInfo,
			}

			checksumDest, err := u.getChecksum(destDir + "/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err := u.getChecksum(fetchDir + "/fileB.txt")
			require.NoError(t, err)
			require.NotEqual(t, checksumDest, checksumFetch)

			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.destContents = append(u.destContents, path)
				return nil
			})
			require.NoError(t, err)

			err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
				u.fetchContents = append(u.fetchContents, path)
				return nil
			})
			require.NoError(t, err)

			exp := []string{destDir, "fileA.txt", "fileB.txt"}

			testErr := u.copyToFinalDest()

			act := []string{}
			err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
				act = append(act, path)
				return nil
			})
			require.NoError(t, err)

			checksumDest, err = u.getChecksum(destDir + "/fileB.txt")
			require.NoError(t, err)
			checksumFetch, err = u.getChecksum(fetchDir + "/fileB.txt")
			require.NoError(t, err)

			assert.NoError(t, testErr)
			assert.Equal(t, 3, len(act))
			assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]) || strings.Contains(act[2], exp[0]))
			assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]) || strings.Contains(act[2], exp[1]))
			assert.True(t, strings.Contains(act[0], exp[2]) || strings.Contains(act[1], exp[2]) || strings.Contains(act[2], exp[2]))
			assert.Equal(t, checksumDest, checksumFetch)
		})
	})

	t.Run("recurse", func(t *testing.T) {
		destDir, err := ioutil.TempDir("", "destDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(destDir)

		ddInfo, err := os.Open(destDir)
		require.NoError(t, err)
		defer ddInfo.Close()

		fetchDir, err := ioutil.TempDir("", "fetchDir_unarchive")
		require.NoError(t, err)
		defer os.RemoveAll(fetchDir)

		dirA, err := ioutil.TempDir(fetchDir, "dirA")
		require.NoError(t, err)
		defer os.RemoveAll(dirA)

		dirB, err := ioutil.TempDir(dirA, "dirB")
		require.NoError(t, err)
		defer os.RemoveAll(dirB)

		dirC, err := ioutil.TempDir(dirB, "dirC")
		require.NoError(t, err)
		defer os.RemoveAll(dirC)

		fileC, err := os.Create(dirC + "/fileC.txt")
		require.NoError(t, err)
		defer os.Remove(fileC.Name())

		fdInfo, err := os.Open(fetchDir)
		require.NoError(t, err)
		defer fdInfo.Close()

		u := &Unarchive{
			Destination: destDir,
			destDir:     ddInfo,
			fetchDir:    fdInfo,
		}

		err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
			u.destContents = append(u.destContents, path)
			return nil
		})
		require.NoError(t, err)

		err = filepath.Walk(u.fetchDir.Name(), func(path string, f os.FileInfo, err error) error {
			u.fetchContents = append(u.fetchContents, path)
			return nil
		})
		require.NoError(t, err)

		exp := []string{destDir, "dirA", "dirB", "dirC", "fileC.txt"}

		testErr := u.copyToFinalDest()

		act := []string{}
		err = filepath.Walk(u.destDir.Name(), func(path string, f os.FileInfo, err error) error {
			act = append(act, path)
			return nil
		})
		require.NoError(t, err)

		assert.NoError(t, testErr)
		assert.Equal(t, 5, len(act))
		assert.True(t, strings.Contains(act[0], exp[0]) || strings.Contains(act[1], exp[0]) || strings.Contains(act[2], exp[0]) || strings.Contains(act[3], exp[0]) || strings.Contains(act[4], exp[0]))
		assert.True(t, strings.Contains(act[0], exp[1]) || strings.Contains(act[1], exp[1]) || strings.Contains(act[2], exp[1]) || strings.Contains(act[3], exp[1]) || strings.Contains(act[4], exp[1]))
		assert.True(t, strings.Contains(act[0], exp[2]) || strings.Contains(act[1], exp[2]) || strings.Contains(act[2], exp[2]) || strings.Contains(act[3], exp[2]) || strings.Contains(act[4], exp[2]))
		assert.True(t, strings.Contains(act[0], exp[3]) || strings.Contains(act[1], exp[3]) || strings.Contains(act[2], exp[3]) || strings.Contains(act[3], exp[3]) || strings.Contains(act[4], exp[3]))
		assert.True(t, strings.Contains(act[0], exp[4]) || strings.Contains(act[1], exp[4]) || strings.Contains(act[2], exp[4]) || strings.Contains(act[3], exp[4]) || strings.Contains(act[4], exp[4]))
	})
}

// TestSetFetchLoc tests setFetchLoc for Unarchive
func TestSetFetchLoc(t *testing.T) {
	t.Parallel()

	srcFile, err := ioutil.TempFile("", "unarchive_test.txt")
	require.NoError(t, err)
	defer os.Remove(srcFile.Name())

	u := &Unarchive{
		Source: srcFile.Name(),
	}

	tmp := os.TempDir()
	s := tmp[len(tmp)-1:]
	expected := tmp + "tmpDirFetch"
	if s != "/" {
		expected = tmp + "/tmpDirFetch"
	}

	err = u.setFetchLoc()
	defer os.RemoveAll(u.fetchLoc)

	assert.NoError(t, err)
	assert.Contains(t, u.fetchLoc, expected)
}

// setupSetDirsAndContents performs some setup required to test
// SetDirsAndContents
func setupSetDirsAndContents(u *Unarchive, nested bool) error {

	err := u.setFetchLoc()
	if err != nil {
		return err
	}

	err = fetchApply(u.fetchLoc, nested)

	return err
}

// fetchApply sets up a temporary fetch location with file(s) based on the
// nested flag
func fetchApply(fetchLoc string, nested bool) error {

	if !nested {
		err := createTempFile(fetchLoc)
		if err != nil {
			return err
		}
	} else {
		_, err := createTempDirAndFiles(fetchLoc)
		if err != nil {
			return err
		}
	}

	return nil
}

// createTempFile will create a temp file in the provided location
// the caller is responsible for removing this file
func createTempFile(location string) error {
	_, err := ioutil.TempFile(location, "fetchFileA.txt")
	return err
}

// createTempDirAndFiles will create a temp directory and two temp files within
// it in the provided location
// the caller is responsible for removing the directory and files
func createTempDirAndFiles(location string) (string, error) {
	nestedFetchDir, err := ioutil.TempDir(location, "unarchive_fetch_nest")
	if err != nil {
		return nestedFetchDir, err
	}

	_, err = ioutil.TempFile(nestedFetchDir, "fetchFileB.txt")
	if err != nil {
		return nestedFetchDir, err
	}

	_, err = ioutil.TempFile(nestedFetchDir, "fetchFileC.txt")
	if err != nil {
		return nestedFetchDir, err
	}

	return nestedFetchDir, nil
}

// zipFiles zips the files in source and places them in destination
func zipFiles(source, destination string) error {
	base := ""

	zipFile, err := os.Create(destination)
	if err != nil {
		return err
	}
	defer zipFile.Close()

	w := zip.NewWriter(zipFile)
	defer w.Close()

	f, err := os.Stat(source)
	if err != nil {
		return err
	}

	if f.IsDir() {
		base = filepath.Base(source)
	}

	err = filepath.Walk(source, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		header, err := zip.FileInfoHeader(f)
		if err != nil {
			return err
		}

		if base != "" {
			header.Name = filepath.Join(base, strings.TrimPrefix(path, source))
		}

		if f.IsDir() {
			header.Name += "/"
		} else {
			header.Method = zip.Deflate
		}

		writer, err := w.CreateHeader(header)
		if err != nil {
			return err
		}

		if f.IsDir() {
			return nil
		}

		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()
		_, err = io.Copy(writer, file)
		return err
	})
	if err != nil {
		return err
	}

	return nil
}
