// Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package cmd

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"reflect"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/minio/mc/pkg/disk"
)

// RUN: go test -v ./... -run Test_FullSuite
func Test_FullSuite(t *testing.T) {
	if os.Getenv("MC_TEST_RUN_FULL_SUITE") != "true" {
		return
	}

	defer func() {
		r := recover()
		if r != nil {
			log.Println(r, string(debug.Stack()))
		}

		postRunCleanup(t)
	}()

	preflightCheck(t)
	// initializeTestSuite builds the mc client and creates local files which are used for testing
	initializeTestSuite(t)

	// Tests within this function depend on one another
	testsThatDependOnOneAnother(t)

	// Alias tests
	AddALIASWithError(t)

	// Basic admin user tests
	AdminUserFunctionalTest(t)

	// Share upload/download
	ShareURLUploadTest(t)
	ShareURLDownloadTest(t)

	// TODO .. for some reason the connection is randomly
	// reset when running curl.
	// ShareURLUploadErrorTests(t)

	// Bucket Error Tests
	CreateBucketUsingInvalidSymbols(t)
	RemoveBucketWithNameTooLong(t)
	RemoveBucketThatDoesNotExist(t)

	// MC_TEST_ENABLE_HTTPS=true
	// needs to be set in order to run these tests
	if protocol == "https://" {
		PutObjectWithSSECHexKey(t)
		GetObjectWithSSEC(t)

		PutObjectWithSSEC(t)
		PutObjectWithSSECPartialPrefixMatch(t)
		PutObjectWithSSECMultipart(t)
		PutObjectWithSSECInvalidKeys(t)
		GetObjectWithSSEC(t)
		GetObjectWithSSECWithoutKey(t)
		CatObjectWithSSEC(t)
		CatObjectWithSSECWithoutKey(t)
		CopyObjectWithSSECToNewBucketWithNewKey(t)
		MirrorTempDirectoryUsingSSEC(t)
		RemoveObjectWithSSEC(t)
	} else {
		PutObjectErrorWithSSECOverHTTP(t)
	}

	// MC_TEST_KMS_KEY=[KEY_NAME]
	// needs to be set in order to run these tests
	if sseKMSKeyName != "" {
		VerifyKMSKey(t)
		PutObjectWithSSEKMS(t)
		PutObjectWithSSEKMSPartialPrefixMatch(t)
		PutObjectWithSSEKMSMultipart(t)
		PutObjectWithSSEKMSInvalidKeys(t)
		GetObjectWithSSEKMS(t)
		CatObjectWithSSEKMS(t)
		CopyObjectWithSSEKMSToNewBucket(t)
		MirrorTempDirectoryUsingSSEKMS(t)
		RemoveObjectWithSSEKMS(t)

		// Error tests
		CopyObjectWithSSEKMSWithOverLappingKeys(t)
	}

	// MC_TEST_ENABLE_SSE_S3=true
	// needs to be set to in order to run these tests.
	if sseS3Enabled {
		PutObjectWithSSES3(t)
		PutObjectWithSSES3PartialPrefixMatch(t)
		PutObjectWithSSES3Multipart(t)
		GetObjectWithSSES3(t)
		CatObjectWithSSES3(t)
		CopyObjectWithSSES3ToNewBucket(t)
		MirrorTempDirectoryUsingSSES3(t)
	}

	if protocol == "https://" && sseKMSKeyName != "" {
		CopyObjectWithSSEKMSToNewBucketWithSSEC(t)
	}

	// (DEPRECATED CLI PARAMETERS)
	if includeDeprecatedMethods {
		fmt.Println("No deprecated methods implemented")
	}
}

func testsThatDependOnOneAnother(t *testing.T) {
	CreateFileBundle()
	// uploadAllFiles uploads all files in FileMap to MainTestBucket
	uploadAllFiles(t)
	// LSObjects saves the output of LS inside *testFile in FileMap
	LSObjects(t)
	// StatObjecsts saves the output of Stat inside *testFile in FileMap
	StatObjects(t)
	// ValidateFileMetaDataPostUpload validates the output of LS and Stat
	ValidateFileMetaData(t)

	// DU tests
	DUBucket(t)

	// Std in/out .. pipe/cat
	CatObjectToStdIn(t)
	CatObjectFromStdin(t)

	// Preserve attributes
	PutObjectPreserveAttributes(t)

	// Mirror
	MirrorTempDirectoryStorageClassReducedRedundancy(t)
	MirrorTempDirectory(t)
	MirrorMinio2MinioWithTagsCopy(t)

	// General object tests
	FindObjects(t)
	FindObjectsUsingName(t)
	FindObjectsUsingNameAndFilteringForTxtType(t)
	FindObjectsLargerThan64Mebibytes(t)
	FindObjectsSmallerThan64Mebibytes(t)
	FindObjectsOlderThan1d(t)
	FindObjectsNewerThen1d(t)
	GetObjectsAndCompareMD5(t)
}

type TestUser struct {
	Username string
	Password string
}

var (
	oneMBSlice               [1048576]byte // 1x Mebibyte
	defaultAlias             = "mintest"
	fileMap                  = make(map[string]*testFile)
	randomLargeString        = "lksdjfljsdklfjklsdjfklksjdf;lsjdk;fjks;djflsdlfkjskldjfklkljsdfljsldkfjklsjdfkljsdklfjklsdjflksjdlfjsdjflsjdflsldfjlsjdflksjdflkjslkdjflksfdj"
	jsonFlag                 = "--json"
	insecureFlag             = "--insecure"
	jsonOutput               = true
	printRawOut              = false
	skipBuild                = false
	mcCmd                    = ".././mc"
	preCmdParameters         = make([]string, 0)
	buildPath                = "../."
	metaPrefix               = "X-Amz-Meta-"
	includeDeprecatedMethods = false

	serverEndpoint = "127.0.0.1:9000"
	acessKey       = "minioadmin"
	secretKey      = "minioadmin"
	protocol       = "http://"
	skipInsecure   = true
	tempDir        = ""
	mainTestBucket string
	sseTestBucket  string
	bucketList     = make([]string, 0)
	userList       = make(map[string]TestUser, 0)

	// ENCRYPTION
	sseHexKey                = "8fe4d820587c427d5cc207d75cb76f3c6874808174b04050fa209206bfd08ebb"
	sseBaseEncodedKey        = "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA"
	invalidSSEBaseEncodedKey = "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5"
	sseBaseEncodedKey2       = "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5YWE"
	sseKMSKeyName            = ""
	sseInvalidKmsKeyName     = ""
	sseS3Enabled             = false

	curlPath      = "/usb/bin/curl"
	HTTPClient    *http.Client
	failIndicator = "!! FAIL !! _______________________ !! FAIL !! _______________________ !! FAIL !!"
)

func openFileAndGetMd5Sum(path string) (md5s string, err error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()
	fb, err := io.ReadAll(f)
	if err != nil {
		return "", err
	}
	md5s = GetMD5Sum(fb)
	return
}

func GetMBSizeInBytes(MB int) int64 {
	return int64(MB * len(oneMBSlice))
}

func initializeTestSuite(t *testing.T) {
	shouldSkipBuild := os.Getenv("MC_TEST_SKIP_BUILD")
	skipBuild, _ = strconv.ParseBool(shouldSkipBuild)
	fmt.Println("SKIP BUILD:", skipBuild)
	if !skipBuild {
		err := BuildCLI()
		if err != nil {
			os.Exit(1)
		}
	}
	envBuildPath := os.Getenv("MC_TEST_BUILD_PATH")
	if envBuildPath != "" {
		buildPath = envBuildPath
	}

	envALIAS := os.Getenv("MC_TEST_ALIAS")
	if envALIAS != "" {
		defaultAlias = envALIAS
	}

	envSecretKey := os.Getenv("MC_TEST_SECRET_KEY")
	if envSecretKey != "" {
		secretKey = envSecretKey
	}

	envAccessKey := os.Getenv("MC_TEST_ACCESS_KEY")
	if envAccessKey != "" {
		acessKey = envAccessKey
	}

	envServerEndpoint := os.Getenv("MC_TEST_SERVER_ENDPOINT")
	if envServerEndpoint != "" {
		serverEndpoint = envServerEndpoint
	}

	envIncludeDeprecated := os.Getenv("MC_TEST_INCLUDE_DEPRECATED")
	includeDeprecatedMethods, _ = strconv.ParseBool(envIncludeDeprecated)

	envKmsKey := os.Getenv("MC_TEST_KMS_KEY")
	if envKmsKey != "" {
		sseKMSKeyName = envKmsKey
	}

	envSSES3Enabled := os.Getenv("MC_TEST_ENABLE_SSE_S3")
	if envSSES3Enabled != "" {
		sseS3Enabled, _ = strconv.ParseBool(envSSES3Enabled)
	}

	envSkipInsecure := os.Getenv("MC_TEST_SKIP_INSECURE")
	if envSkipInsecure != "" {
		skipInsecure, _ = strconv.ParseBool(envSkipInsecure)
	}

	envEnableHTTP := os.Getenv("MC_TEST_ENABLE_HTTPS")
	EnableHTTPS, _ := strconv.ParseBool(envEnableHTTP)
	if EnableHTTPS {
		protocol = "https://"
	}

	envCMD := os.Getenv("MC_TEST_BINARY_PATH")
	if envCMD != "" {
		mcCmd = envCMD
	}

	var err error
	tempDir, err = os.MkdirTemp("", "test-")
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	for i := 0; i < len(oneMBSlice); i++ {
		oneMBSlice[i] = byte(rand.Intn(250))
	}

	for i := 0; i < 10; i++ {
		tmpNameMap["aaa"+strconv.Itoa(i)] = false
	}
	for i := 0; i < 10; i++ {
		tmpNameMap["bbb"+strconv.Itoa(i)] = false
	}
	for i := 0; i < 10; i++ {
		tmpNameMap["ccc"+strconv.Itoa(i)] = false
	}
	for i := 0; i < 10; i++ {
		tmpNameMap["ddd"+strconv.Itoa(i)] = false
	}

	HTTPClient = &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: skipInsecure},
		},
	}

	if jsonOutput {
		preCmdParameters = append(preCmdParameters, jsonFlag)
	}

	if skipInsecure {
		preCmdParameters = append(preCmdParameters, insecureFlag)
	}

	CreateTestUsers()

	_, err = RunMC(
		"alias",
		"set",
		defaultAlias,
		protocol+serverEndpoint,
		acessKey,
		secretKey,
	)
	fatalIfError(err, t)

	out, err := RunMC("--version")
	fatalIfError(err, t)
	fmt.Println(out)

	preRunCleanup()

	mainTestBucket = CreateBucket(t)
	sseTestBucket = CreateBucket(t)
}

func preflightCheck(t *testing.T) {
	out, err := exec.Command("which", "curl").Output()
	fatalIfError(err, t)
	if len(out) == 0 {
		fatalMsgOnly("No curl found, output from 'which curl': "+string(out), t)
	}
	curlPath = string(out)
}

func CreateTestUsers() {
	userList["user1"] = TestUser{
		Username: "user1",
		Password: "user1-password",
	}
	userList["user2"] = TestUser{
		Username: "user2",
		Password: "user2-password",
	}
	userList["user3"] = TestUser{
		Username: "user3",
		Password: "user3-password",
	}
}

func CreateFileBundle() {
	createFile(newTestFile{
		tag:          "0M",
		prefix:       "",
		extension:    ".jpg",
		storageClass: "",
		sizeInMBS:    0,
		tags:         map[string]string{"name": "0M"},
		// uploadShouldFail:   false,
		addToGlobalFileMap: true,
	})
	createFile(newTestFile{
		tag:          "1M",
		prefix:       "",
		extension:    ".txt",
		storageClass: "REDUCED_REDUNDANCY",
		sizeInMBS:    1,
		metaData:     map[string]string{"name": "1M"},
		tags:         map[string]string{"tag1": "1M-tag"},
		// uploadShouldFail:   false,
		addToGlobalFileMap: true,
	})
	createFile(newTestFile{
		tag:          "2M",
		prefix:       "LVL1",
		extension:    ".jpg",
		storageClass: "REDUCED_REDUNDANCY",
		sizeInMBS:    2,
		metaData:     map[string]string{"name": "2M"},
		// uploadShouldFail:   false,
		addToGlobalFileMap: true,
	})
	createFile(newTestFile{
		tag:          "3M",
		prefix:       "LVL1/LVL2",
		extension:    ".png",
		storageClass: "",
		sizeInMBS:    3,
		metaData:     map[string]string{"name": "3M"},
		// uploadShouldFail:   false,
		addToGlobalFileMap: true,
	})
	createFile(newTestFile{
		tag:          "65M",
		prefix:       "LVL1/LVL2/LVL3",
		extension:    ".exe",
		storageClass: "",
		sizeInMBS:    65,
		metaData:     map[string]string{"name": "65M", "tag1": "value1"},
		// uploadShouldFail:   false,
		addToGlobalFileMap: true,
	})
}

var tmpNameMap = make(map[string]bool)

func GetRandomName() string {
	for i := range tmpNameMap {
		if tmpNameMap[i] == false {
			tmpNameMap[i] = true
			return i
		}
	}
	return uuid.NewString()
}

func CreateBucket(t *testing.T) (bucketPath string) {
	bucketName := "test-" + GetRandomName()
	bucketPath = defaultAlias + "/" + bucketName
	out, err := RunMC("mb", bucketPath)
	if err != nil {
		t.Fatalf("Unable to create bucket (%s) err: %s", bucketPath, out)
		return
	}
	bucketList = append(bucketList, bucketPath)
	out, err = RunMC("stat", defaultAlias+"/"+bucketName)
	if err != nil {
		t.Fatalf("Unable to ls stat (%s) err: %s", defaultAlias+"/"+bucketName, out)
		return
	}
	if !strings.Contains(out, bucketName) {
		t.Fatalf("stat output does not contain bucket name (%s)", bucketName)
	}
	return
}

func AddALIASWithError(t *testing.T) {
	out, err := RunMC(
		"alias",
		"set",
		defaultAlias,
		protocol+serverEndpoint,
		acessKey,
		"random-invalid-secret-that-will-not-work",
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func AdminUserFunctionalTest(t *testing.T) {
	user1Bucket := CreateBucket(t)

	user1File := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "user1",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"admin",
		"user",
		"add",
		defaultAlias,
		userList["user1"].Username,
		userList["user1"].Password,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"admin",
		"user",
		"list",
		defaultAlias,
	)
	fatalIfErrorWMsg(err, out, t)
	userOutput, err := parseUserMessageListOutput(out)
	fatalIfErrorWMsg(err, out, t)

	user1found := false
	for i := range userOutput {
		if userOutput[i].AccessKey == userList["user1"].Username {
			user1found = true
		}
	}

	if !user1found {
		fatalMsgOnly(fmt.Sprintf("did not find user %s when running admin user list --json", userList["user1"].Username), t)
	}

	out, err = RunMC(
		"admin",
		"policy",
		"attach",
		defaultAlias,
		"readwrite",
		"--user="+userList["user1"].Username,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"alias",
		"set",
		userList["user1"].Username,
		protocol+serverEndpoint,
		userList["user1"].Username,
		userList["user1"].Password,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		user1File.diskFile.Name(),
		user1Bucket+"/"+user1File.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func ShareURLUploadErrorTests(t *testing.T) {
	shareURLErrorBucket := CreateBucket(t)

	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "presigned-error",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"share",
		"upload",
		shareURLErrorBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	shareMsg, err := parseShareMessageFromJSONOutput(out)
	fatalIfErrorWMsg(err, out, t)

	finalURL := strings.Replace(shareMsg.ShareURL, "<FILE>", file.diskFile.Name(), -1)
	splitCommand := strings.Split(finalURL, " ")

	if skipInsecure {
		splitCommand = append(splitCommand, "--insecure")
	}

	bucketOnly := strings.Replace(shareURLErrorBucket, defaultAlias+"/", "", -1)

	// Modify base url bucket path
	newCmd := make([]string, len(splitCommand))
	copy(newCmd, splitCommand)
	newCmd[1] = strings.Replace(newCmd[1], bucketOnly, "fake-bucket-name", -1)
	out, _ = RunCommand(newCmd[0], newCmd[1:]...)
	curlFatalIfNoErrorTag(out, t)

	// Modify -F key=X
	newCmd = make([]string, len(splitCommand))
	copy(newCmd, splitCommand)
	for i := range newCmd {
		if strings.HasPrefix(newCmd[i], "key=") {
			newCmd[i] = "key=fake-object-name"
			break
		}
	}
	out, _ = RunCommand(newCmd[0], newCmd[1:]...)
	curlFatalIfNoErrorTag(out, t)
}

func ShareURLUploadTest(t *testing.T) {
	ShareURLTestBucket := CreateBucket(t)

	file := createFile(newTestFile{
		addToGlobalFileMap: true,
		tag:                "presigned-upload",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"share",
		"upload",
		ShareURLTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	shareMsg, err := parseShareMessageFromJSONOutput(out)
	fatalIfErrorWMsg(err, out, t)

	finalURL := strings.Replace(shareMsg.ShareURL, "<FILE>", file.diskFile.Name(), -1)
	splitCommand := strings.Split(finalURL, " ")

	if skipInsecure {
		splitCommand = append(splitCommand, "--insecure")
	}

	_, err = exec.Command(splitCommand[0], splitCommand[1:]...).CombinedOutput()
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"stat",
		ShareURLTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	statMsg, err := parseStatSingleObjectJSONOutput(out)
	fatalIfError(err, t)

	if statMsg.ETag != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got md5sum (%s)", file.md5Sum, file.md5Sum), t)
	}
}

func ShareURLDownloadTest(t *testing.T) {
	ShareURLTestBucket := CreateBucket(t)
	file := createFile(newTestFile{
		addToGlobalFileMap: true,
		tag:                "presigned-download",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		file.diskFile.Name(),
		ShareURLTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"share",
		"download",
		ShareURLTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	shareMsg, err := parseShareMessageFromJSONOutput(out)
	fatalIfErrorWMsg(err, out, t)

	resp, err := HTTPClient.Get(shareMsg.ShareURL)
	fatalIfError(err, t)

	downloadedFile, err := io.ReadAll(resp.Body)
	fatalIfError(err, t)

	md5sum := GetMD5Sum(downloadedFile)
	if md5sum != file.md5Sum {
		fatalMsgOnly(
			fmt.Sprintf("expecting md5sum (%s) but got md5sum (%s)", file.md5Sum, md5sum),
			t,
		)
	}
}

func PutObjectPreserveAttributes(t *testing.T) {
	AttrTestBucket := CreateBucket(t)
	file := fileMap["1M"]
	out, err := RunMC(
		"cp",
		"-a",
		file.diskFile.Name(),
		AttrTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"stat",
		AttrTestBucket+"/"+file.fileNameWithPrefix,
	)
	fatalIfError(err, t)

	stats, err := parseStatSingleObjectJSONOutput(out)
	fatalIfError(err, t)

	attr, err := disk.GetFileSystemAttrs(file.diskFile.Name())
	fatalIfError(err, t)
	if attr != stats.Metadata["X-Amz-Meta-Mc-Attrs"] {
		fatalMsgOnly(fmt.Sprintf("expecting file attributes (%s) but got file attributes (%s)", attr, stats.Metadata["X-Amz-Meta-Mc-Attrs"]), t)
	}
}

func MirrorTempDirectoryStorageClassReducedRedundancy(t *testing.T) {
	MirrorBucket := CreateBucket(t)
	out, err := RunMC(
		"mirror",
		"--storage-class", "REDUCED_REDUNDANCY",
		tempDir,
		MirrorBucket,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC("ls", "-r", MirrorBucket)
	fatalIfError(err, t)

	fileList, err := parseLSJSONOutput(out)
	fatalIfError(err, t)

	for i, f := range fileMap {
		fileFound := false

		for _, o := range fileList {
			if o.Key == f.fileNameWithoutPath {
				fileMap[i].MinioLS = o
				fileFound = true
			}
		}

		if !fileFound {
			t.Fatalf("File was not uploaded: %s", f.fileNameWithPrefix)
		}
	}
}

func MirrorTempDirectory(t *testing.T) {
	MirrorBucket := CreateBucket(t)

	out, err := RunMC(
		"mirror",
		tempDir,
		MirrorBucket,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC("ls", "-r", MirrorBucket)
	fatalIfError(err, t)

	fileList, err := parseLSJSONOutput(out)
	fatalIfError(err, t)

	for i, f := range fileMap {
		fileFound := false

		for _, o := range fileList {
			if o.Key == f.fileNameWithoutPath {
				fileMap[i].MinioLS = o
				fileFound = true
			}
		}

		if !fileFound {
			t.Fatalf("File was not uploaded: %s", f.fileNameWithPrefix)
		}
	}
}

type tagListResult struct {
	Tagset    map[string]string `json:"tagset"`
	Status    string            `json:"status"`
	URL       string            `json:"url"`
	VersionID string            `json:"versionID"`
}

func MirrorMinio2MinioWithTagsCopy(t *testing.T) {
	TargetBucket := CreateBucket(t)
	out, err := RunMC(
		"mirror",
		mainTestBucket,
		TargetBucket,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC("tag", "list", TargetBucket, "-r")
	fatalIfErrorWMsg(err, out, t)
	var targetTagsList []tagListResult
	outStrs := bufio.NewScanner(strings.NewReader(out))
	for outStrs.Scan() {
		outStr := outStrs.Text()
		if outStr == "" {
			continue
		}
		tagList := new(tagListResult)
		if err := json.Unmarshal([]byte(outStr), tagList); err != nil {
			fatalIfErrorWMsg(err, out, t)
		}
		targetTagsList = append(targetTagsList, *tagList)
	}

	for _, f := range fileMap {
		fileFound := false

		for _, o := range targetTagsList {
			if strings.Contains(o.URL, f.fileNameWithPrefix) {
				fileFound = true
				if !reflect.DeepEqual(f.tags, o.Tagset) {
					fatalMsgOnly(fmt.Sprintf("expecting tags (%s) but got tags (%s)", f.tags, o.Tagset), t)
				}
			}
		}

		if !fileFound {
			t.Fatalf("File was not mirrored: %s", f.fileNameWithPrefix)
		}
	}
}

func CatObjectFromStdin(t *testing.T) {
	objectName := "pipe-test-object"
	CatEchoBucket := CreateBucket(t)

	file := fileMap["1M"]

	cmdCAT := exec.Command(
		"cat",
		file.diskFile.Name(),
	)

	p := []string{
		"pipe",
		CatEchoBucket + "/" + objectName,
	}
	if skipInsecure {
		p = append(p, "--insecure")
	}

	cmdMC := exec.Command(mcCmd, p...)

	r, w := io.Pipe()
	defer r.Close()
	defer w.Close()

	cmdCAT.Stdout = w
	cmdMC.Stdin = r

	err := cmdMC.Start()
	fatalIfError(err, t)
	err = cmdCAT.Start()
	fatalIfError(err, t)

	err = cmdCAT.Wait()
	fatalIfError(err, t)
	w.Close()
	err = cmdMC.Wait()
	fatalIfError(err, t)
	r.Close()

	outB, err := RunMC(
		"cat",
		CatEchoBucket+"/"+objectName,
	)
	fatalIfErrorWMsg(err, outB, t)

	md5SumCat := GetMD5Sum([]byte(outB))
	if file.md5Sum != md5SumCat {
		fatalMsgOnly(
			fmt.Sprintf("expecting md5sum (%s) but got md5sum (%s)", file.md5Sum, md5SumCat),
			t,
		)
	}
}

func CatObjectToStdIn(t *testing.T) {
	file := fileMap["1M"]
	out, err := RunMC(
		"cat",
		mainTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
	md5Sum := GetMD5Sum([]byte(out))
	if md5Sum != file.md5Sum {
		fatalMsgOnly(
			fmt.Sprintf("expecting md5sum (%s) but got md5sum (%s)", file.md5Sum, md5Sum),
			t,
		)
	}
}

func VerifyKMSKey(t *testing.T) {
	out, err := RunMC(
		"admin",
		"kms",
		"key",
		"list",
		defaultAlias,
	)
	fatalIfError(err, t)
	keyMsg := new(kmsKeysMsg)
	err = json.Unmarshal([]byte(out), keyMsg)
	fatalIfError(err, t)
	sseInvalidKmsKeyName = uuid.NewString()
	found := false
	invalidKeyFound := false
	for _, v := range keyMsg.Keys {
		if v == sseKMSKeyName {
			found = true
			break
		}
		if v == sseInvalidKmsKeyName {
			invalidKeyFound = true
		}
	}
	if !found {
		fatalMsgOnly(fmt.Sprintf("expected to find kms key %s but got these keys: %v", sseKMSKeyName, keyMsg.Keys), t)
	}
	if invalidKeyFound {
		fatalMsgOnly("tried to create invalid uuid kms key but for some reason it overlapped with an already existing key", t)
	}
}

func PutObjectWithSSEKMSPartialPrefixMatch(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput-kms-prefix-test",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms",
		sseTestBucket+"/"+file.fileNameWithoutPath+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSEKMS(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms",
		sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSEKMSMultipart(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encmultiput-kms",
		sizeInMBS:          68,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms",
		sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSEKMSInvalidKeys(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encerror-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseInvalidKmsKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func GetObjectWithSSES3(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encget-s3",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-s3="+sseTestBucket,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func CatObjectWithSSES3(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "enccat-s3",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-s3="+sseTestBucket,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cat",
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
	catMD5Sum := GetMD5Sum([]byte(out))

	if catMD5Sum != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf(
			"expected md5sum %s but we got %s",
			file.md5Sum,
			catMD5Sum,
		), t)
	}

	if int64(len(out)) != file.diskStat.Size() {
		fatalMsgOnly(fmt.Sprintf(
			"file size is %d but we got %d",
			file.diskStat.Size(),
			len(out),
		), t)
	}

	fatalIfErrorWMsg(
		err,
		"cat length: "+strconv.Itoa(len(out))+" -- file length:"+strconv.Itoa(int(file.diskStat.Size())),
		t,
	)
}

func CopyObjectWithSSES3ToNewBucket(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encbucketcopy-s3",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-s3="+sseTestBucket,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	TargetSSEBucket := CreateBucket(t)

	out, err = RunMC(
		"cp",
		"--enc-s3="+TargetSSEBucket,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func MirrorTempDirectoryUsingSSES3(t *testing.T) {
	MirrorBucket := CreateBucket(t)

	subDir := "encmirror-s3"

	f1 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror1-s3",
		sizeInMBS:          1,
	})

	f2 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror2-s3",
		sizeInMBS:          2,
	})

	f3 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror3-s3",
		sizeInMBS:          4,
	})

	files := append([]*testFile{}, f1, f2, f3)

	out, err := RunMC(
		"mirror",
		"--enc-s3="+MirrorBucket,
		tempDir+string(os.PathSeparator)+subDir,
		MirrorBucket,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC("ls", "-r", MirrorBucket)
	fatalIfError(err, t)

	fileList, err := parseLSJSONOutput(out)
	fatalIfError(err, t)

	for i, f := range files {
		fileFound := false

		for _, o := range fileList {
			if o.Key == f.fileNameWithoutPath {
				files[i].MinioLS = o
				fileFound = true
			}
		}

		if !fileFound {
			fatalMsgOnly(fmt.Sprintf(
				"File was not uploaded: %s",
				f.fileNameWithPrefix,
			), t)
		}

		out, err := RunMC("stat", MirrorBucket+"/"+files[i].MinioLS.Key)
		fatalIfError(err, t)
		stat, err := parseStatSingleObjectJSONOutput(out)
		fatalIfError(err, t)
		files[i].MinioStat = stat

		foundKmsTag := false
		for ii := range stat.Metadata {
			if ii == amzObjectSSE {
				foundKmsTag = true
				break
			}
		}

		if !foundKmsTag {
			fmt.Println(stat)
			fatalMsgOnly(amzObjectSSEKMSKeyID+" not found for object "+files[i].MinioLS.Key, t)
		}

	}
}

func PutObjectWithSSES3PartialPrefixMatch(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput-s3-prefix-test",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-s3="+sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name(),
		sseTestBucket,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSES3(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput-s3",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-s3="+sseTestBucket,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSES3Multipart(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encmultiput-s3",
		sizeInMBS:          68,
	})

	out, err := RunMC(
		"cp",
		"--enc-s3="+sseTestBucket,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func GetObjectWithSSEKMS(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encget-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func PutObjectWithSSECMultipart(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encmultiput",
		sizeInMBS:          68,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func CatObjectWithSSEKMS(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "enccat-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cat",
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
	catMD5Sum := GetMD5Sum([]byte(out))

	if catMD5Sum != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf(
			"expected md5sum %s but we got %s",
			file.md5Sum,
			catMD5Sum,
		), t)
	}

	if int64(len(out)) != file.diskStat.Size() {
		fatalMsgOnly(fmt.Sprintf(
			"file size is %d but we got %d",
			file.diskStat.Size(),
			len(out),
		), t)
	}

	fatalIfErrorWMsg(
		err,
		"cat length: "+strconv.Itoa(len(out))+" -- file length:"+strconv.Itoa(int(file.diskStat.Size())),
		t,
	)
}

func PutObjectWithSSECPartialPrefixMatch(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput-prefix-test",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"/"+file.fileNameWithoutPath+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSECHexKey(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encputhex",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseHexKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectWithSSEC(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
}

func PutObjectErrorWithSSECOverHTTP(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encput-http",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func PutObjectWithSSECInvalidKeys(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encerror-dep",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+invalidSSEBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func GetObjectWithSSECHexKey(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encgethex",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseHexKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseHexKey,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func GetObjectWithSSEC(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encget",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func GetObjectWithSSECWithoutKey(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encerror",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+"-get",
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func CatObjectWithSSEC(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "enccat",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cat",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)
	catMD5Sum := GetMD5Sum([]byte(out))

	if catMD5Sum != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf(
			"expected md5sum %s but we got %s",
			file.md5Sum,
			catMD5Sum,
		), t)
	}

	if int64(len(out)) != file.diskStat.Size() {
		fatalMsgOnly(fmt.Sprintf(
			"file size is %d but we got %d",
			file.diskStat.Size(),
			len(out),
		), t)
	}

	fatalIfErrorWMsg(
		err,
		"cat length: "+strconv.Itoa(len(out))+" -- file length:"+strconv.Itoa(int(file.diskStat.Size())),
		t,
	)
}

func CopyObjectWithSSEKMSWithOverLappingKeys(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encbucketcopy-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	TargetSSEBucket := CreateBucket(t)

	out, err = RunMC(
		"cp",
		"--enc-kms="+TargetSSEBucket+"="+sseKMSKeyName,
		"--enc-kms="+TargetSSEBucket+"="+sseKMSKeyName,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func CopyObjectWithSSEKMSToNewBucket(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encbucketcopy-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	TargetSSEBucket := CreateBucket(t)

	out, err = RunMC(
		"cp",
		"--enc-kms="+TargetSSEBucket+"="+sseKMSKeyName,
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		"--enc-kms="+TargetSSEBucket+"="+sseKMSKeyName,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func CopyObjectWithSSEKMSToNewBucketWithSSEC(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encbucketcopy-kms-c",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	TargetSSEBucket := CreateBucket(t)

	out, err = RunMC(
		"cp",
		"--enc-c="+TargetSSEBucket+"="+sseBaseEncodedKey,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		"--enc-c="+TargetSSEBucket+"="+sseBaseEncodedKey,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func MirrorTempDirectoryUsingSSEKMS(t *testing.T) {
	MirrorBucket := CreateBucket(t)

	subDir := "encmirror-kms"

	f1 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror1-kms",
		sizeInMBS:          1,
	})

	f2 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror2-kms",
		sizeInMBS:          2,
	})

	f3 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror3-kms",
		sizeInMBS:          4,
	})

	files := append([]*testFile{}, f1, f2, f3)

	out, err := RunMC(
		"mirror",
		"--enc-kms="+MirrorBucket+"="+sseKMSKeyName,
		tempDir+string(os.PathSeparator)+subDir,
		MirrorBucket,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC("ls", "-r", MirrorBucket)
	fatalIfError(err, t)

	fileList, err := parseLSJSONOutput(out)
	fatalIfError(err, t)

	for i, f := range files {
		fileFound := false

		for _, o := range fileList {
			if o.Key == f.fileNameWithoutPath {
				files[i].MinioLS = o
				fileFound = true
			}
		}

		if !fileFound {
			fatalMsgOnly(fmt.Sprintf(
				"File was not uploaded: %s",
				f.fileNameWithPrefix,
			), t)
		}

		out, err := RunMC("stat", MirrorBucket+"/"+files[i].MinioLS.Key)
		fatalIfError(err, t)
		stat, err := parseStatSingleObjectJSONOutput(out)
		fatalIfError(err, t)
		files[i].MinioStat = stat

		foundKmsTag := false
		for ii, v := range stat.Metadata {
			if ii == amzObjectSSEKMSKeyID {
				foundKmsTag = true
				if !strings.HasSuffix(v, sseKMSKeyName) {
					fatalMsgOnly("invalid KMS key for object "+files[i].MinioLS.Key, t)
					break
				}
			}
		}

		if !foundKmsTag {
			fatalMsgOnly(amzObjectSSEKMSKeyID+" not found for object "+files[i].MinioLS.Key, t)
		}

	}
}

func RemoveObjectWithSSEKMS(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encrm-kms",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-kms="+sseTestBucket+"="+sseKMSKeyName,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"rm",
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"stat",
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func CatObjectWithSSECWithoutKey(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encerror",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cat",
		sseTestBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+"-cat",
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func RemoveObjectWithSSEC(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encrm",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"rm",
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"stat",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfNoErrorWMsg(err, out, t)
}

func MirrorTempDirectoryUsingSSEC(t *testing.T) {
	MirrorBucket := CreateBucket(t)

	subDir := "encmirror"

	f1 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror1",
		sizeInMBS:          1,
	})

	f2 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror2",
		sizeInMBS:          2,
	})

	f3 := createFile(newTestFile{
		addToGlobalFileMap: false,
		subDir:             subDir,
		tag:                "encmirror3",
		sizeInMBS:          4,
	})

	files := append([]*testFile{}, f1, f2, f3)

	out, err := RunMC(
		"mirror",
		"--enc-c="+MirrorBucket+"="+sseBaseEncodedKey,
		tempDir+string(os.PathSeparator)+subDir,
		MirrorBucket,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC("ls", "-r", MirrorBucket)
	fatalIfError(err, t)

	fileList, err := parseLSJSONOutput(out)
	fatalIfError(err, t)

	for i, f := range files {
		fileFound := false

		for _, o := range fileList {
			if o.Key == f.fileNameWithoutPath {
				files[i].MinioLS = o
				fileFound = true
			}
		}

		if !fileFound {
			fatalMsgOnly(fmt.Sprintf(
				"File was not uploaded: %s",
				f.fileNameWithPrefix,
			), t)
		}

		out, err := RunMC(
			"stat",
			"--enc-c="+MirrorBucket+"="+sseBaseEncodedKey,
			MirrorBucket+"/"+files[i].MinioLS.Key,
		)
		fatalIfError(err, t)
		_, err = parseStatSingleObjectJSONOutput(out)
		fatalIfError(err, t)

	}
}

func CopyObjectWithSSECToNewBucketWithNewKey(t *testing.T) {
	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "encbucketcopy",
		sizeInMBS:          1,
	})

	out, err := RunMC(
		"cp",
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		file.diskFile.Name(),
		sseTestBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	TargetSSEBucket := CreateBucket(t)

	out, err = RunMC(
		"cp",
		"--enc-c="+TargetSSEBucket+"="+sseBaseEncodedKey2,
		"--enc-c="+sseTestBucket+"="+sseBaseEncodedKey,
		sseTestBucket+"/"+file.fileNameWithoutPath,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
	)
	fatalIfErrorWMsg(err, out, t)

	out, err = RunMC(
		"cp",
		"--enc-c="+TargetSSEBucket+"="+sseBaseEncodedKey2,
		TargetSSEBucket+"/"+file.fileNameWithoutPath,
		file.diskFile.Name()+".download",
	)
	fatalIfErrorWMsg(err, out, t)

	md5s, err := openFileAndGetMd5Sum(file.diskFile.Name() + ".download")
	fatalIfError(err, t)
	if md5s != file.md5Sum {
		fatalMsgOnly(fmt.Sprintf("expecting md5sum (%s) but got sum (%s)", file.md5Sum, md5s), t)
	}
}

func uploadAllFiles(t *testing.T) {
	for _, v := range fileMap {
		parameters := make([]string, 0)
		parameters = append(parameters, "cp")

		if v.storageClass != "" {
			parameters = append(parameters, "--storage-class", v.storageClass)
		}

		if len(v.metaData) > 0 {
			parameters = append(parameters, "--attr")
			meta := ""
			for i, v := range v.metaData {
				meta += i + "=" + v + ";"
			}
			meta = strings.TrimSuffix(meta, ";")
			parameters = append(parameters, meta)
		}
		if len(v.tags) > 0 {
			parameters = append(parameters, "--tags")
			tags := ""
			for i, v := range v.tags {
				tags += i + "=" + v + ";"
			}
			tags = strings.TrimSuffix(tags, ";")
			parameters = append(parameters, tags)
		}

		parameters = append(parameters, v.diskFile.Name())

		if v.prefix != "" {
			parameters = append(
				parameters,
				mainTestBucket+"/"+v.fileNameWithPrefix,
			)
		} else {
			parameters = append(
				parameters,
				mainTestBucket+"/"+v.fileNameWithoutPath,
			)
		}

		_, err := RunMC(parameters...)
		if err != nil {
			t.Fatal(err)
		}
	}
}

func OD(t *testing.T) {
	LocalBucketPath := CreateBucket(t)

	file := fileMap["65M"]
	out, err := RunMC(
		"od",
		"if="+file.diskFile.Name(),
		"of="+LocalBucketPath+"/od/"+file.fileNameWithoutPath,
		"parts=10",
	)

	fatalIfError(err, t)
	odMsg, err := parseSingleODMessageJSONOutput(out)
	fatalIfError(err, t)

	if odMsg.TotalSize != file.diskStat.Size() {
		t.Fatalf(
			"Expected (%d) bytes to be uploaded but only uploaded (%d) bytes",
			odMsg.TotalSize,
			file.diskStat.Size(),
		)
	}

	if odMsg.Parts != 10 {
		t.Fatalf(
			"Expected upload parts to be (10) but they were (%d)",
			odMsg.Parts,
		)
	}

	if odMsg.Type != "FStoS3" {
		t.Fatalf(
			"Expected type to be (FStoS3) but got (%s)",
			odMsg.Type,
		)
	}

	if odMsg.PartSize != uint64(file.diskStat.Size())/10 {
		t.Fatalf(
			"Expected part size to be (%d) but got (%d)",
			file.diskStat.Size()/10,
			odMsg.PartSize,
		)
	}

	out, err = RunMC(
		"od",
		"of="+file.diskFile.Name(),
		"if="+LocalBucketPath+"/od/"+file.fileNameWithoutPath,
		"parts=10",
	)

	fatalIfError(err, t)
	fmt.Println(out)
	odMsg, err = parseSingleODMessageJSONOutput(out)
	fatalIfError(err, t)

	if odMsg.TotalSize != file.diskStat.Size() {
		t.Fatalf(
			"Expected (%d) bytes to be uploaded but only uploaded (%d) bytes",
			odMsg.TotalSize,
			file.diskStat.Size(),
		)
	}

	if odMsg.Parts != 10 {
		t.Fatalf(
			"Expected upload parts to be (10) but they were (%d)",
			odMsg.Parts,
		)
	}

	if odMsg.Type != "S3toFS" {
		t.Fatalf(
			"Expected type to be (FStoS3) but got (%s)",
			odMsg.Type,
		)
	}

	if odMsg.PartSize != uint64(file.diskStat.Size())/10 {
		t.Fatalf(
			"Expected part size to be (%d) but got (%d)",
			file.diskStat.Size()/10,
			odMsg.PartSize,
		)
	}
}

func MvFromDiskToMinio(t *testing.T) {
	LocalBucketPath := CreateBucket(t)

	file := createFile(newTestFile{
		addToGlobalFileMap: false,
		tag:                "10Move",
		prefix:             "",
		extension:          ".txt",
		storageClass:       "",
		sizeInMBS:          1,
		metaData:           map[string]string{"name": "10Move"},
		tags:               map[string]string{"tag1": "10Move-tag"},
	})

	out, err := RunMC(
		"mv",
		file.diskFile.Name(),
		LocalBucketPath+"/"+file.fileNameWithoutPath,
	)

	fatalIfError(err, t)
	splitReturn := bytes.Split([]byte(out), []byte{10})

	mvMSG, err := parseSingleCPMessageJSONOutput(string(splitReturn[0]))
	fatalIfError(err, t)

	if mvMSG.TotalCount != 1 {
		t.Fatalf("Expected count to be 1 but got (%d)", mvMSG.TotalCount)
	}

	if mvMSG.Size != file.diskStat.Size() {
		t.Fatalf(
			"Expected size to be (%d) but got (%d)",
			file.diskStat.Size(),
			mvMSG.Size,
		)
	}

	if mvMSG.Status != "success" {
		t.Fatalf(
			"Expected status to be (success) but got (%s)",
			mvMSG.Status,
		)
	}

	statMSG, err := parseSingleAccountStatJSONOutput(string(splitReturn[1]))
	fatalIfError(err, t)

	if statMSG.Transferred != file.diskStat.Size() {
		t.Fatalf(
			"Expected transfeered to be (%d) but got (%d)",
			file.diskStat.Size(),
			statMSG.Transferred,
		)
	}

	if statMSG.Total != file.diskStat.Size() {
		t.Fatalf(
			"Expected total to be (%d) but got (%d)",
			file.diskStat.Size(),
			statMSG.Total,
		)
	}

	if statMSG.Status != "success" {
		t.Fatalf(
			"Expected status to be (success) but got (%s)",
			statMSG.Status,
		)
	}
}

func DUBucket(t *testing.T) {
	var totalFileSize int64
	for _, v := range fileMap {
		totalFileSize += v.MinioStat.Size
	}

	out, err := RunMC("du", mainTestBucket)
	fatalIfError(err, t)

	duList, err := parseDUJSONOutput(out)
	fatalIfError(err, t)
	if len(duList) != 1 {
		fatalMsgOnly("Expected 1 result to be returned", t)
	}
	if duList[0].Size != totalFileSize {
		fatalMsgOnly(
			fmt.Sprintf("total size to be %d but got %d", totalFileSize, duList[0].Size),
			t,
		)
	}
}

func LSObjects(t *testing.T) {
	out, err := RunMC("ls", "-r", mainTestBucket)
	fatalIfError(err, t)

	fileList, err := parseLSJSONOutput(out)
	fatalIfError(err, t)

	for i, f := range fileMap {
		fileFound := false

		for _, o := range fileList {
			if o.Key == f.fileNameWithPrefix {
				fileMap[i].MinioLS = o
				fileFound = true
			}
		}

		if !fileFound {
			t.Fatalf("File was not uploaded: %s", f.fileNameWithPrefix)
		}
	}
}

func StatObjects(t *testing.T) {
	for i, v := range fileMap {

		out, err := RunMC(
			"stat",
			mainTestBucket+"/"+v.fileNameWithPrefix,
		)
		fatalIfError(err, t)

		fileMap[i].MinioStat, err = parseStatSingleObjectJSONOutput(out)
		fatalIfError(err, t)

		if fileMap[i].MinioStat.Key == "" {
			t.Fatalf("Unable to stat Minio object (%s)", v.fileNameWithPrefix)
		}

	}
}

func ValidateFileMetaData(t *testing.T) {
	for _, f := range fileMap {
		validateFileLSInfo(t, f)
		validateObjectMetaData(t, f)
		// validateContentType(t, f)
	}
}

func FindObjects(t *testing.T) {
	out, err := RunMC("find", mainTestBucket)
	fatalIfError(err, t)

	findList, err := parseFindJSONOutput(out)
	fatalIfError(err, t)

	for _, v := range fileMap {

		found := false
		for _, vv := range findList {
			if strings.HasSuffix(vv.Key, v.MinioLS.Key) {
				found = true
			}
		}

		if !found {
			t.Fatalf("File (%s) not found by 'find' command", v.MinioLS.Key)
		}
	}
}

func FindObjectsUsingName(t *testing.T) {
	for _, v := range fileMap {

		out, err := RunMC(
			"find",
			mainTestBucket,
			"--name",
			v.fileNameWithoutPath,
		)

		fatalIfError(err, t)
		info, err := parseFindSingleObjectJSONOutput(out)
		fatalIfError(err, t)
		if !strings.HasSuffix(info.Key, v.MinioLS.Key) {
			t.Fatalf("Invalid key (%s) when searching for (%s)", info.Key, v.MinioLS.Key)
		}

	}
}

func FindObjectsUsingNameAndFilteringForTxtType(t *testing.T) {
	out, err := RunMC(
		"find",
		mainTestBucket,
		"--name",
		"*.txt",
	)
	fatalIfError(err, t)

	findList, err := parseFindJSONOutput(out)
	fatalIfError(err, t)

	for _, v := range fileMap {
		if v.extension != ".txt" {
			continue
		}

		found := false
		for _, vv := range findList {
			if strings.HasSuffix(vv.Key, v.MinioLS.Key) {
				found = true
			}
		}

		if !found {
			t.Fatalf("File (%s) not found by 'find' command", v.MinioLS.Key)
		}
	}
}

func FindObjectsSmallerThan64Mebibytes(t *testing.T) {
	out, err := RunMC(
		"find",
		mainTestBucket,
		"--smaller",
		"64MB",
	)
	fatalIfError(err, t)

	findList, err := parseFindJSONOutput(out)
	fatalIfError(err, t)

	for _, v := range fileMap {
		if v.diskStat.Size() > GetMBSizeInBytes(64) {
			continue
		}

		found := false
		for _, vv := range findList {
			if strings.HasSuffix(vv.Key, v.MinioLS.Key) {
				found = true
			}
		}

		if !found {
			t.Fatalf("File (%s) not found by 'find' command", v.MinioLS.Key)
		}
	}
}

func FindObjectsLargerThan64Mebibytes(t *testing.T) {
	out, err := RunMC(
		"find",
		mainTestBucket,
		"--larger",
		"64MB",
	)
	fatalIfError(err, t)

	findList, err := parseFindJSONOutput(out)
	fatalIfError(err, t)

	for _, v := range fileMap {
		if v.diskStat.Size() < GetMBSizeInBytes(64) {
			continue
		}

		found := false
		for _, vv := range findList {
			if strings.HasSuffix(vv.Key, v.MinioLS.Key) {
				found = true
			}
		}

		if !found {
			t.Fatalf("File (%s) not found by 'find' command", v.MinioLS.Key)
		}
	}
}

func FindObjectsOlderThan1d(t *testing.T) {
	out, err := RunMC(
		"find",
		mainTestBucket,
		"--older-than",
		"1d",
	)
	fatalIfError(err, t)

	findList, err := parseFindJSONOutput(out)
	fatalIfError(err, t)

	if len(findList) > 0 {
		t.Fatalf("We should not have found any files which are older then 1 day")
	}
}

func FindObjectsNewerThen1d(t *testing.T) {
	out, err := RunMC(
		"find",
		mainTestBucket,
		"--newer-than",
		"1d",
	)
	fatalIfError(err, t)

	findList, err := parseFindJSONOutput(out)
	fatalIfError(err, t)

	for _, v := range fileMap {

		found := false
		for _, vv := range findList {
			if strings.HasSuffix(vv.Key, v.MinioLS.Key) {
				found = true
			}
		}

		if !found {
			t.Fatalf("File (%s) not found by 'find' command", v.MinioLS.Key)
		}
	}
}

func GetObjectsAndCompareMD5(t *testing.T) {
	for _, v := range fileMap {

		// make sure old downloads are not in our way
		_ = os.Remove(tempDir + "/" + v.fileNameWithoutPath + ".downloaded")

		_, err := RunMC(
			"cp",
			mainTestBucket+"/"+v.fileNameWithPrefix,
			tempDir+"/"+v.fileNameWithoutPath+".downloaded",
		)
		fatalIfError(err, t)

		downloadedFile, err := os.Open(
			tempDir + "/" + v.fileNameWithoutPath + ".downloaded",
		)
		fatalIfError(err, t)

		fileBytes, err := io.ReadAll(downloadedFile)
		fatalIfError(err, t)
		md5sum := GetMD5Sum(fileBytes)

		if v.md5Sum != md5sum {
			t.Fatalf(
				"The downloaded file md5sum is wrong: original-md5(%s) downloaded-md5(%s)",
				v.md5Sum,
				md5sum,
			)
		}
	}
}

func CreateBucketUsingInvalidSymbols(t *testing.T) {
	bucketNameMap := make(map[string]string)
	bucketNameMap["name-too-big"] = randomLargeString
	bucketNameMap["!"] = "symbol!"
	bucketNameMap["@"] = "symbol@"
	bucketNameMap["#"] = "symbol#"
	bucketNameMap["$"] = "symbol$"
	bucketNameMap["%"] = "symbol%"
	bucketNameMap["^"] = "symbol^"
	bucketNameMap["&"] = "symbol&"
	bucketNameMap["*"] = "symbol*"
	bucketNameMap["("] = "symbol("
	bucketNameMap[")"] = "symbol)"
	bucketNameMap["{"] = "symbol{"
	bucketNameMap["}"] = "symbol}"
	bucketNameMap["["] = "symbol["
	bucketNameMap["]"] = "symbol]"

	for _, v := range bucketNameMap {
		_, err := RunMC("mb", defaultAlias+"/"+v)
		if err == nil {
			t.Fatalf("We should not have been able to create a bucket with the name: %s", v)
		}
	}
}

func RemoveBucketThatDoesNotExist(t *testing.T) {
	randomID := uuid.NewString()
	out, _ := RunMC(
		"rb",
		defaultAlias+"/"+randomID,
	)
	errMSG, _ := parseSingleErrorMessageJSONOutput(out)
	validateErrorMSGValues(
		t,
		errMSG,
		"error",
		"Unable to validate",
		"does not exist",
	)
}

func RemoveBucketWithNameTooLong(t *testing.T) {
	randomID := uuid.NewString()
	out, _ := RunMC(
		"rb",
		defaultAlias+"/"+randomID+randomID,
	)
	errMSG, _ := parseSingleErrorMessageJSONOutput(out)
	validateErrorMSGValues(
		t,
		errMSG,
		"error",
		"Unable to validate",
		"Bucket name cannot be longer than 63 characters",
	)
}

func UploadToUnknownBucket(t *testing.T) {
	randomBucketID := uuid.NewString()
	parameters := append(
		[]string{},
		"cp",
		fileMap["1M"].diskFile.Name(),
		defaultAlias+"/"+randomBucketID+"-test-should-not-exist"+"/"+fileMap["1M"].fileNameWithoutPath,
	)

	_, err := RunMC(parameters...)
	if err == nil {
		t.Fatalf("We should not have been able to upload to bucket: %s", randomBucketID)
	}
}

func preRunCleanup() {
	for i := range tmpNameMap {
		_, _ = RunMC("rb", "--force", "--dangerous", defaultAlias+"/test-"+i)
	}
}

func postRunCleanup(t *testing.T) {
	var err error
	var berr error
	var out string

	err = os.RemoveAll(tempDir)
	if err != nil {
		fmt.Println(err)
	}

	for _, v := range bucketList {
		out, berr = RunMC("rb", "--force", "--dangerous", v)
		if berr != nil {
			fmt.Printf("Unable to remove bucket (%s) err: %s //  out: %s", v, berr, out)
		}
	}

	for _, v := range userList {
		_, _ = RunMC(
			"admin",
			"user",
			"remove",
			defaultAlias,
			v.Username,
		)
	}

	fatalIfError(berr, t)
	fatalIfError(err, t)
}

func validateFileLSInfo(t *testing.T, file *testFile) {
	if file.diskStat.Size() != int64(file.MinioLS.Size) {
		t.Fatalf(
			"File and minio object are not the same size - Object (%d) vs File (%d)",
			file.MinioLS.Size,
			file.diskStat.Size(),
		)
	}
	// if file.md5Sum != file.findOutput.Etag {
	// 	t.Fatalf("File and file.findOutput do not have the same md5Sum - Object (%s) vs File (%s)", file.findOutput.Etag, file.md5Sum)
	// }
	if file.storageClass != "" {
		if file.storageClass != file.MinioLS.StorageClass {
			t.Fatalf(
				"File and minio object do not have the same storage class - Object (%s) vs File (%s)",
				file.MinioLS.StorageClass,
				file.storageClass,
			)
		}
	} else {
		if file.MinioLS.StorageClass != "STANDARD" {
			t.Fatalf(
				"Minio object was expected to have storage class (STANDARD) but it was (%s)",
				file.MinioLS.StorageClass,
			)
		}
	}
}

func validateObjectMetaData(t *testing.T, file *testFile) {
	for i, v := range file.metaData {
		found := false

		for ii, vv := range file.MinioStat.Metadata {
			if metaPrefix+http.CanonicalHeaderKey(i) == ii {
				found = true
				if v != vv {
					fmt.Println("------------------------")
					fmt.Println("META CHECK")
					fmt.Println(file.MinioStat.Metadata)
					fmt.Println(file.metaData)
					fmt.Println("------------------------")
					t.Fatalf("Meta values are not the same v1(%s) v2(%s)", v, vv)
				}
			}
		}

		if !found {
			fmt.Println("------------------------")
			fmt.Println("META CHECK")
			fmt.Println(file.MinioStat.Metadata)
			fmt.Println(file.metaData)
			fmt.Println("------------------------")
			t.Fatalf("Meta tag(%s) not found", i)
		}

	}
}

// func validateContentType(t *testing.T, file *testFile) {
// 	value, ok := file.MinioStat.Metadata["Content-Type"]
// 	if !ok {
// 		t.Fatalf("File (%s) did not have a content type", file.fileNameWithPrefix)
// 		return
// 	}
//
// 	contentType := mime.TypeByExtension(file.extension)
// 	if contentType != value {
// 		log.Println(file)
// 		log.Println(file.MinioLS)
// 		log.Println(file.extension)
// 		log.Println(file.MinioStat)
// 		t.Fatalf("Content types on file (%s) do not match, extension(%s) File(%s) MinIO object(%s)", file.fileNameWithPrefix, file.extension, contentType, file.MinioStat.Metadata["Content-Type"])
// 	}
// }

func GetSource(skip int) (out string) {
	pc := make([]uintptr, 3) // at least 1 entry needed
	runtime.Callers(skip, pc)
	f := runtime.FuncForPC(pc[0])
	file, line := f.FileLine(pc[0])
	sn := strings.Split(f.Name(), ".")
	var name string
	if sn[len(sn)-1] == "func1" {
		name = sn[len(sn)-2]
	} else {
		name = sn[len(sn)-1]
	}
	out = file + ":" + fmt.Sprint(line) + ":" + name
	return
}

func GetMD5Sum(data []byte) string {
	md5Writer := md5.New()
	md5Writer.Write(data)
	return fmt.Sprintf("%x", md5Writer.Sum(nil))
}

func curlFatalIfNoErrorTag(msg string, t *testing.T) {
	if !strings.Contains(msg, "<Error>") {
		fmt.Println(failIndicator)
		fmt.Println(msg)
		t.Fatal(msg)
	}
}

func fatalMsgOnly(msg string, t *testing.T) {
	fmt.Println(failIndicator)
	t.Fatal(msg)
}

func fatalIfNoErrorWMsg(err error, msg string, t *testing.T) {
	if err == nil {
		fmt.Println(failIndicator)
		fmt.Println(msg)
		t.Fatal(err)
	}
}

func fatalIfErrorWMsg(err error, msg string, t *testing.T) {
	if err != nil {
		fmt.Println(failIndicator)
		fmt.Println(msg)
		t.Fatal(err)
	}
}

func fatalIfError(err error, t *testing.T) {
	if err != nil {
		fmt.Println(failIndicator)
		t.Fatal(err)
	}
}

func parseFindJSONOutput(out string) (findList []*findMessage, err error) {
	findList = make([]*findMessage, 0)
	splitList := bytes.Split([]byte(out), []byte{10})

	for _, v := range splitList {
		if len(v) < 1 {
			continue
		}
		line := new(findMessage)
		err = json.Unmarshal(v, line)
		if err != nil {
			return
		}
		findList = append(findList, line)
	}

	if printRawOut {
		fmt.Println("FIND LIST ------------------------------")
		for _, v := range findList {
			fmt.Println(v)
		}
		fmt.Println(" ------------------------------")
	}
	return
}

func parseDUJSONOutput(out string) (duList []duMessage, err error) {
	duList = make([]duMessage, 0)
	splitList := bytes.Split([]byte(out), []byte{10})

	for _, v := range splitList {
		if len(v) < 1 {
			continue
		}
		line := duMessage{}
		err = json.Unmarshal(v, &line)
		if err != nil {
			return
		}
		duList = append(duList, line)
	}

	if printRawOut {
		fmt.Println("DU LIST ------------------------------")
		for _, v := range duList {
			fmt.Println(v)
		}
		fmt.Println(" ------------------------------")
	}
	return
}

func parseLSJSONOutput(out string) (lsList []contentMessage, err error) {
	lsList = make([]contentMessage, 0)
	splitList := bytes.Split([]byte(out), []byte{10})

	for _, v := range splitList {
		if len(v) < 1 {
			continue
		}
		line := contentMessage{}
		err = json.Unmarshal(v, &line)
		if err != nil {
			return
		}
		lsList = append(lsList, line)
	}

	if printRawOut {
		fmt.Println("LS LIST ------------------------------")
		for _, v := range lsList {
			fmt.Println(v)
		}
		fmt.Println(" ------------------------------")
	}
	return
}

func parseFindSingleObjectJSONOutput(out string) (findInfo contentMessage, err error) {
	err = json.Unmarshal([]byte(out), &findInfo)
	if err != nil {
		return
	}

	if printRawOut {
		fmt.Println("FIND SINGLE OBJECT ------------------------------")
		fmt.Println(findInfo)
		fmt.Println(" ------------------------------")
	}
	return
}

func parseStatSingleObjectJSONOutput(out string) (stat statMessage, err error) {
	err = json.Unmarshal([]byte(out), &stat)
	if err != nil {
		return
	}

	if printRawOut {
		fmt.Println("STAT ------------------------------")
		fmt.Println(stat)
		fmt.Println(" ------------------------------")
	}
	return
}

// We have to wrap the error output because the console
// printing mechanism for json marshals into an anonymous
// object before printing, see cmd/error.go line 70
type errorMessageWrapper struct {
	Error  errorMessage `json:"error"`
	Status string       `json:"status"`
}

func validateErrorMSGValues(
	t *testing.T,
	errMSG errorMessageWrapper,
	TypeToValidate string,
	MessageToValidate string,
	CauseToValidate string,
) {
	if TypeToValidate != "" {
		if !strings.Contains(errMSG.Error.Type, TypeToValidate) {
			t.Fatalf(
				"Expected error.Error.Type to contain (%s) - but got (%s)",
				TypeToValidate,
				errMSG.Error.Type,
			)
		}
	}
	if MessageToValidate != "" {
		if !strings.Contains(errMSG.Error.Message, MessageToValidate) {
			t.Fatalf(
				"Expected error.Error.Message to contain (%s) - but got (%s)",
				MessageToValidate,
				errMSG.Error.Message,
			)
		}
	}
	if CauseToValidate != "" {
		if !strings.Contains(errMSG.Error.Cause.Message, CauseToValidate) {
			t.Fatalf(
				"Expected error.Error.Cause.Message to contain (%s) - but got (%s)",
				CauseToValidate,
				errMSG.Error.Cause.Message,
			)
		}
	}
}

func parseUserMessageListOutput(out string) (users []*userMessage, err error) {
	users = make([]*userMessage, 0)
	splitList := bytes.Split([]byte(out), []byte{10})
	for _, v := range splitList {
		if len(v) < 1 {
			continue
		}
		msg := new(userMessage)
		err = json.Unmarshal(v, msg)
		if err != nil {
			return
		}
		users = append(users, msg)
	}

	if printRawOut {
		fmt.Println("USER LIST ------------------------------")
		for _, v := range users {
			fmt.Println(v)
		}
		fmt.Println(" ------------------------------")
	}

	return
}

func parseShareMessageFromJSONOutput(out string) (share *shareMessage, err error) {
	share = new(shareMessage)
	err = json.Unmarshal([]byte(out), share)
	return
}

func parseSingleErrorMessageJSONOutput(out string) (errMSG errorMessageWrapper, err error) {
	err = json.Unmarshal([]byte(out), &errMSG)
	if err != nil {
		return
	}

	fmt.Println("ERROR ------------------------------")
	fmt.Println(errMSG)
	fmt.Println(" ------------------------------")
	return
}

func parseSingleODMessageJSONOutput(out string) (odMSG odMessage, err error) {
	err = json.Unmarshal([]byte(out), &odMSG)
	if err != nil {
		return
	}

	return
}

func parseSingleAccountStatJSONOutput(out string) (stat accountStat, err error) {
	err = json.Unmarshal([]byte(out), &stat)
	if err != nil {
		return
	}

	return
}

func parseSingleCPMessageJSONOutput(out string) (cpMSG copyMessage, err error) {
	err = json.Unmarshal([]byte(out), &cpMSG)
	if err != nil {
		return
	}

	return
}

type newTestFile struct {
	tag          string // The tag used to identify the file inside the FileMap. This tag is also used in the objects name.
	prefix       string // Prefix for the object name ( not including the object name itself)
	extension    string
	storageClass string
	sizeInMBS    int
	// uploadShouldFail bool
	metaData map[string]string
	tags     map[string]string

	addToGlobalFileMap bool
	// sub directory path to place the file in
	// tempDir+/+subDir
	subDir string
}

type testFile struct {
	newTestFile

	// File on disk
	diskFile *os.File
	// File info on disk
	diskStat os.FileInfo
	// md5sum at the time of creation
	md5Sum string
	// File name without full path
	fileNameWithoutPath string
	// File name with assigned prefix
	fileNameWithPrefix string

	// These field are not automatically populated unless
	// the file is created at the initialization phase of
	// the test suite: testsThatDependOnOneAnother()
	// Minio mc stat output
	MinioStat statMessage
	// Minio mc ls output
	MinioLS contentMessage
}

func (f *testFile) String() (out string) {
	out = fmt.Sprintf(
		"Size: %d || Name: %s || md5Sum: %s",
		f.diskStat.Size(),
		f.fileNameWithoutPath,
		f.md5Sum,
	)
	return
}

func createFile(nf newTestFile) (newTestFile *testFile) {
	var newFile *os.File
	var err error
	if nf.subDir != "" {

		err = os.MkdirAll(
			tempDir+string(os.PathSeparator)+nf.subDir,
			0o755)
		if err != nil {
			log.Println("Could not make additional dir:", err)
			os.Exit(1)
		}

		newFile, err = os.CreateTemp(
			tempDir+string(os.PathSeparator)+nf.subDir,
			nf.tag+"-*"+nf.extension,
		)

	} else {
		newFile, err = os.CreateTemp(tempDir, nf.tag+"-*"+nf.extension)
	}

	if err != nil {
		log.Println("Could not make file:", err)
		os.Exit(1)
	}

	md5Writer := md5.New()
	for i := 0; i < nf.sizeInMBS; i++ {
		n, err := newFile.Write(oneMBSlice[:])
		mn, merr := md5Writer.Write(oneMBSlice[:])
		if err != nil || merr != nil {
			log.Println(err)
			log.Println(merr)
			return nil
		}
		if n != len(oneMBSlice) {
			log.Println("Did not write 1MB to file")
			return nil
		}
		if mn != len(oneMBSlice) {
			log.Println("Did not write 1MB to md5sum writer")
			return nil
		}
	}
	splitName := strings.Split(newFile.Name(), string(os.PathSeparator))
	fileNameWithoutPath := splitName[len(splitName)-1]
	md5sum := fmt.Sprintf("%x", md5Writer.Sum(nil))
	stats, err := newFile.Stat()
	if err != nil {
		return nil
	}
	newTestFile = &testFile{
		md5Sum:              md5sum,
		fileNameWithoutPath: fileNameWithoutPath,
		diskFile:            newFile,
		diskStat:            stats,
	}

	newTestFile.tag = nf.tag
	newTestFile.metaData = nf.metaData
	newTestFile.storageClass = nf.storageClass
	newTestFile.sizeInMBS = nf.sizeInMBS
	newTestFile.tags = nf.tags
	newTestFile.prefix = nf.prefix
	newTestFile.extension = nf.extension

	if nf.prefix != "" {
		newTestFile.fileNameWithPrefix = nf.prefix + "/" + fileNameWithoutPath
	} else {
		newTestFile.fileNameWithPrefix = fileNameWithoutPath
	}
	if nf.addToGlobalFileMap {
		fileMap[nf.tag] = newTestFile
	}
	return newTestFile
}

func BuildCLI() error {
	wd, _ := os.Getwd()
	fmt.Println("WORKING DIR:", wd)
	fmt.Println("go build -o", mcCmd, buildPath)
	os.Remove(mcCmd)
	out, err := exec.Command("go", "build", "-o", mcCmd, buildPath).CombinedOutput()
	if err != nil {
		log.Println("BUILD OUT:", out)
		log.Println(err)
		panic(err)
	}
	err = os.Chmod(mcCmd, 0o777)
	if err != nil {
		panic(err)
	}
	return nil
}

func RunMC(parameters ...string) (out string, err error) {
	var outBytes []byte
	var outErr error

	fmt.Println("")
	fmt.Println(time.Now().Format("2006-01-02T15:04:05.000"), "||", GetSource(3))
	fmt.Println(mcCmd, strings.Join(preCmdParameters, " "), strings.Join(parameters, " "))

	outBytes, outErr = exec.Command(mcCmd, append(preCmdParameters, parameters...)...).CombinedOutput()
	if printRawOut {
		fmt.Println(string(outBytes))
	}
	out = string(outBytes)
	err = outErr
	return
}

func RunCommand(cmd string, parameters ...string) (out string, err error) {
	fmt.Println("")
	fmt.Println(time.Now().Format("2006-01-02T15:04:05.000"), "||", GetSource(3))
	fmt.Println(cmd, strings.Join(parameters, " "))
	var outBytes []byte
	var outErr error

	outBytes, outErr = exec.Command(cmd, parameters...).CombinedOutput()
	if printRawOut {
		fmt.Println(string(outBytes))
	}
	out = string(outBytes)
	err = outErr
	return
}
