endpoints

package
v0.0.0-...-9ea7be0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 7, 2022 License: Apache-2.0 Imports: 48 Imported by: 0

Documentation

Overview

Example (Alerts)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

const userClearedNotification = `{"userid":"600f2a0806b6c70071d3d174","notifications":{"topics":[{"name":"topic c","config":{"method":{"ui":true,"sms":true,"email":true}}},{"name":"topic d","config":{"method":{"ui":true,"sms":true,"email":true}}}],"hints":[],"uinotifications":[]},"userconfig":{"name":"Niko Bellic","email":"niko@spicule.co.uk","cell":"","data_collection":"unknown"}}`

// Expecting 2 gets for users file
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}

mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	// Simulate returning the user file with notifications in it
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(userJSONNotification))),
	},
	// Second get receives the notification-cleared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(userClearedNotification))),
	},
}

// Expecting a put for the cleared user file
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/notifications/600f2a0806b6c70071d3d174.json"), Body: bytes.NewReader([]byte(userClearedNotification)),
	},
}

// Simulate returning ok for put
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)

obj := notifications.UINotificationObj{
	Topic:     "test-data-source",
	Message:   "New Data Source Available",
	Timestamp: time.Time{},
	UserID:    "600f2a0806b6c70071d3d174",
}
svcs.Notifications.AddNotification(obj)

setTestAuth0Config(&svcs)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/notification/alerts", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)

resp = executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)
Output:

ensure-valid: 200
[
    {
        "topic": "test-data-source",
        "message": "New Data Source Available",
        "timestamp": "2021-02-01T01:01:01Z",
        "userid": "600f2a0806b6c70071d3d174"
    }
]
ensure-valid: 200
[]
Example (Alerts_empty)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Expecting 1 get for users file
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}

// No file!
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)

obj := notifications.UINotificationObj{
	Topic:     "test-data-source",
	Message:   "New Data Source Available",
	Timestamp: time.Time{},
	UserID:    "600f2a0806b6c70071d3d174",
}
svcs.Notifications.AddNotification(obj)

setTestAuth0Config(&svcs)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/notification/alerts", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)
Output:

ensure-valid: 200
[]
Example (CalculateTotals_Fail_AB)
q, err := quantModel.ReadQuantificationFile("./test-data/AB.bin")
fmt.Printf("%v\n", err)

result, err := calculateTotals(q, []int{90, 91, 95})

fmt.Printf("%v|%v\n", result, err)
Output:

<nil>
map[]|Quantification must be for Combined detectors
Example (CalculateTotals_Fail_NoPMC)
q, err := quantModel.ReadQuantificationFile("./test-data/combined.bin")
fmt.Printf("%v\n", err)

result, err := calculateTotals(q, []int{68590, 68591, 68595})

fmt.Printf("%v|%v\n", result, err)
Output:

<nil>
map[]|Quantification had no valid data for ROI PMCs
Example (CalculateTotals_Success)
q, err := quantModel.ReadQuantificationFile("./test-data/combined.bin")
fmt.Printf("%v\n", err)

result, err := calculateTotals(q, []int{90, 91, 95})

fmt.Printf("%v|%v\n", result, err)
Output:

<nil>
map[CaO_%:7.5057006 FeO-T_%:10.621034 SiO2_%:41.48377 TiO2_%:0.7424]|<nil>
Example (DataExpressionHandler_Delete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "def456": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": false,
        "creator": {
            "name": "The sharer",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path), Body: bytes.NewReader([]byte(`{
    "def456": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path), Body: bytes.NewReader([]byte(`{}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Delete finds file missing, ERROR
req, _ := http.NewRequest("DELETE", "/data-expression/abc123", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds empty file, ERROR
req, _ = http.NewRequest("DELETE", "/data-expression/abc123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete cant find item, ERROR
req, _ = http.NewRequest("DELETE", "/data-expression/abc999", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds item, OK
req, _ = http.NewRequest("DELETE", "/data-expression/abc123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item but from wrong user, ERROR
req, _ = http.NewRequest("DELETE", "/data-expression/shared-def456", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item, OK
req, _ = http.NewRequest("DELETE", "/data-expression/shared-def456", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
abc123 not found

404
abc123 not found

404
abc999 not found

200
{
    "def456": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}

401
def456 not owned by 600f2a0806b6c70071d3d174

200
{}
Example (DataExpressionHandler_Get)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// POST not implemented! Should return 405
req, _ := http.NewRequest("GET", "/data-expression/abc123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405
Example (DataExpressionHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	{
		// Note: No comments!
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"ghi789": {
		"name": "Iron %",
		"expression": "element(\"Fe\", \"%\")",
		"type": "TernaryPlot",
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "niko@spicule.co.uk"
		}
	}
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/data-expression", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/data-expression", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/data-expression", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{}

200
{}

200
{
    "abc123": {
        "name": "Calcium weight%",
        "expression": "element(\"Ca\", \"%\")",
        "type": "ContextImage",
        "comments": "comments for abc123 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "def456": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "shared-ghi789": {
        "name": "Iron %",
        "expression": "element(\"Fe\", \"%\")",
        "type": "TernaryPlot",
        "comments": "",
        "shared": true,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}
Example (DataExpressionHandler_Post)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path), Body: bytes.NewReader([]byte(`{
    "id16": {
        "name": "Sodium weight%",
        "expression": "element(\"Na\", \"%\")",
        "type": "ContextImage",
        "comments": "sodium comment here",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path), Body: bytes.NewReader([]byte(`{
    "id17": {
        "name": "Sodium weight%",
        "expression": "element(\"Na\", \"%\")",
        "type": "ContextImage",
        "comments": "sodium comment here",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path), Body: bytes.NewReader([]byte(`{
    "abc123": {
        "name": "Calcium weight%",
        "expression": "element(\"Ca\", \"%\")",
        "type": "ContextImage",
        "comments": "comments for abc123 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "def456": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "id18": {
        "name": "Sodium weight%",
        "expression": "element(\"Na\", \"%\")",
        "type": "ContextImage",
        "comments": "sodium comment here",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"id16", "id17", "id18"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
	"name": "Sodium weight%",
	"expression": "element(\"Na\", \"%\")",
	"type": "ContextImage",
	"comments": "sodium comment here"
}`

// File not in S3, should work
req, _ := http.NewRequest("POST", "/data-expression", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should work
req, _ = http.NewRequest("POST", "/data-expression", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File already contains stuff, this is added
req, _ = http.NewRequest("POST", "/data-expression", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "id16": {
        "name": "Sodium weight%",
        "expression": "element(\"Na\", \"%\")",
        "type": "ContextImage",
        "comments": "sodium comment here",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}

200
{
    "id17": {
        "name": "Sodium weight%",
        "expression": "element(\"Na\", \"%\")",
        "type": "ContextImage",
        "comments": "sodium comment here",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}

200
{
    "abc123": {
        "name": "Calcium weight%",
        "expression": "element(\"Ca\", \"%\")",
        "type": "ContextImage",
        "comments": "comments for abc123 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "def456": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "id18": {
        "name": "Sodium weight%",
        "expression": "element(\"Na\", \"%\")",
        "type": "ContextImage",
        "comments": "sodium comment here",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}
Example (DataExpressionHandler_Put)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
}

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path), Body: bytes.NewReader([]byte(`{
    "abc123": {
        "name": "Calcium weight%",
        "expression": "element(\"Ca\", \"%\")",
        "type": "ContextImage",
        "comments": "comments for abc123 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "def456": {
        "name": "Iron Int",
        "expression": "element(\"Fe\", \"int\")",
        "type": "TernaryPlot",
        "comments": "Iron comment",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
		"name": "Iron Int",
        "expression": "element(\"Fe\", \"int\")",
        "type": "TernaryPlot",
        "comments": "Iron comment"
	}`

// File not in S3, not found
req, _ := http.NewRequest("PUT", "/data-expression/aaa111", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, not found
req, _ = http.NewRequest("PUT", "/data-expression/aaa111", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File already this id, should overwrite
req, _ = http.NewRequest("PUT", "/data-expression/def456", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File doesn't contain this id, not found
req, _ = http.NewRequest("PUT", "/data-expression/aaa111", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Can't edit shared ids
req, _ = http.NewRequest("PUT", "/data-expression/shared-111", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
aaa111 not found

404
aaa111 not found

200
{
    "abc123": {
        "name": "Calcium weight%",
        "expression": "element(\"Ca\", \"%\")",
        "type": "ContextImage",
        "comments": "comments for abc123 expression",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "def456": {
        "name": "Iron Int",
        "expression": "element(\"Fe\", \"int\")",
        "type": "TernaryPlot",
        "comments": "Iron comment",
        "shared": false,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}

404
aaa111 not found

400
Cannot edit shared expressions
Example (DataExpressionHandler_Share)
sharedExpressionsContents := `{
		"aaa333": {
			"name": "Calcium Error",
			"expression": "element(\"Ca\", \"err\")",
			"type": "TernaryPlot",
			"comments": "calcium comments",
			"shared": false,
			"creator": {
				"name": "The sharer",
				"user_id": "600f2a0806b6c70071d3d174",
				"email": "niko@spicule.co.uk"
			}
		}
	}`
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprS3Path),
	},
	// Reading shared file to add to it
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedExpressionsContents))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedExpressionsContents))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedExpressionsContents))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(exprFile))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedExpressionsContents))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(exprSharedS3Path), Body: bytes.NewReader([]byte(`{
    "aaa333": {
        "name": "Calcium Error",
        "expression": "element(\"Ca\", \"err\")",
        "type": "TernaryPlot",
        "comments": "calcium comments",
        "shared": false,
        "creator": {
            "name": "The sharer",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    "ddd222": {
        "name": "Iron Error",
        "expression": "element(\"Fe\", \"err\")",
        "type": "BinaryPlot",
        "comments": "comments for def456 expression",
        "shared": true,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"ddd222"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = ""

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/data-expression/abc123", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("POST", "/share/data-expression/abc123", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File missing the id being shared
req, _ = http.NewRequest("POST", "/share/data-expression/zzz222", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains ID, share OK
req, _ = http.NewRequest("POST", "/share/data-expression/def456", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
abc123 not found

404
abc123 not found

404
zzz222 not found

200
"ddd222"
Example (DatasetCustomImagesDelete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/UNALIGNED/unaligned-222.jpg"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/UNALIGNED/unaligned-222.jpg"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/RGBU/nirgbuv-333.tif"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/RGBU/nirgbuv-333.tif"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/MATCHED/watson-222.json"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/MATCHED/watson-222.json"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/MATCHED/watson-222.jpg"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	nil, // unaligned missing
	{},
	nil, // rgbu missing
	{},
	nil, // matched JSON missing
	{},  // matched json
	{},  // matched image
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

// Missing type
req, _ := http.NewRequest("DELETE", "/dataset/images/abc-123/watson-111.jpg", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Bad type
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/badtype/watson-222.jpg", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Unaligned, fail
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/unaligned/unaligned-222.jpg", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Unaligned, OK
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/unaligned/unaligned-222.jpg", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// RGBU, fail
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/rgbu/nirgbuv-333.tif", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// RGBU, OK
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/rgbu/nirgbuv-333.tif", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Matched, fail
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/matched/watson-222.jpg", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Matched, OK
req, _ = http.NewRequest("DELETE", "/dataset/images/abc-123/matched/watson-222.jpg", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405

400
Invalid custom image type: "badtype"

404
unaligned-222.jpg not found

200

404
nirgbuv-333.tif not found

200

404
watson-222.json not found

200
Example (DatasetCustomImagesGet_matched)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/MATCHED/watson-123.json"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/MATCHED/watson-33.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/abc-123/dataset.bin"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "aligned-beam-pmc": 77,
    "matched-image": "watson-33.png",
    "x-offset": 11,
    "y-offset": 12,
    "x-scale": 1.4,
    "y-scale": 1.5
}`))),
	},
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111/matched/watson-123.jpg", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset/images/abc-123/matched/watson-33.png", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
dataset custom image meta not found

200
{
    "alignedImageLink": "",
    "download-link": "https:///dataset/download/abc-123/watson-33.png?loadCustomType=matched",
    "aligned-beam-pmc": 77,
    "matched-image": "watson-33.png",
    "x-offset": 11,
    "y-offset": 12,
    "x-scale": 1.4,
    "y-scale": 1.5
}
Example (DatasetCustomImagesGet_rgbu)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111/rgbu/rgbu.tif", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "download-link": "https:///dataset/download/abc-111/rgbu.tif?loadCustomType=rgbu"
}
Example (DatasetCustomImagesGet_unaligned)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111/unaligned/mastcamZ.png", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "download-link": "https:///dataset/download/abc-111/mastcamZ.png?loadCustomType=unaligned"
}
Example (DatasetCustomImagesList_matched)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{Bucket: aws.String(artifactManualUploadBucket), Prefix: aws.String("dataset-addons/abc-111/MATCHED/")},
	{Bucket: aws.String(artifactManualUploadBucket), Prefix: aws.String("dataset-addons/abc-123/MATCHED/")},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	nil,
	{
		Contents: []*s3.Object{
			{Key: aws.String("dataset-addons/abc-123/RGBU/watson-123.jpg")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/watson-123.json")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/shouldnt be here.txt")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/watson-777.png")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/watson-777-meta.json")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/watson-33.png")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/watson-33.json")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/mosaic.png")},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111/matched", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset/images/abc-123/matched", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
custom images not found

200
[
    "watson-123.jpg",
    "watson-33.png"
]
Example (DatasetCustomImagesList_missingtype)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
404 page not found
Example (DatasetCustomImagesList_rgbu)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{Bucket: aws.String(artifactManualUploadBucket), Prefix: aws.String("dataset-addons/abc-111/RGBU/")},
	{Bucket: aws.String(artifactManualUploadBucket), Prefix: aws.String("dataset-addons/abc-123/RGBU/")},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	nil,
	{
		Contents: []*s3.Object{
			{Key: aws.String("dataset-addons/abc-123/RGBU/nirgbuv.tif")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/shouldnt be here.txt")},
			{Key: aws.String("dataset-addons/abc-123/RGBU/another.tif")},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111/rgbu", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset/images/abc-123/rgbu", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
custom images not found

200
[
    "nirgbuv.tif",
    "shouldnt be here.txt",
    "another.tif"
]
Example (DatasetCustomImagesList_unaligned)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{Bucket: aws.String(artifactManualUploadBucket), Prefix: aws.String("dataset-addons/abc-111/UNALIGNED/")},
	{Bucket: aws.String(artifactManualUploadBucket), Prefix: aws.String("dataset-addons/abc-123/UNALIGNED/")},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	nil,
	{
		Contents: []*s3.Object{
			{Key: aws.String("dataset-addons/abc-123/UNALIGNED/mastcam-123.jpg")},
			{Key: aws.String("dataset-addons/abc-123/UNALIGNED/shouldnt be here.txt")},
			{Key: aws.String("dataset-addons/abc-123/UNALIGNED/mosaic.png")},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/images/abc-111/unaligned", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset/images/abc-123/unaligned", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
custom images not found

200
[
    "mastcam-123.jpg",
    "shouldnt be here.txt",
    "mosaic.png"
]
Example (DatasetCustomImagesPost_badfilename)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/dataset/images/abc-111/rgbu/noextension-file-name", bytes.NewBuffer([]byte{84, 73, 70}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Invalid file name: "noextension-file-name"
Example (DatasetCustomImagesPost_badtype)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

// Bad type
req, _ := http.NewRequest("POST", "/dataset/images/abc-111/badtype/nirgbuv.png", bytes.NewBuffer([]byte{84, 73, 70}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Invalid custom image type: "badtype"
Example (DatasetCustomImagesPost_matched)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Expecting uploaded image and JSON file
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/MATCHED/watson-444.json"), Body: bytes.NewReader([]byte(`{
    "aligned-beam-pmc": 88,
    "matched-image": "watson-444.png",
    "x-offset": 11,
    "y-offset": 22,
    "x-scale": 1.23,
    "y-scale": 1.1
}`)),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/MATCHED/watson-444.png"), Body: bytes.NewReader([]byte("PNG")),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

// Missing aligned-beam-pmc
req, _ := http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22", bytes.NewBuffer([]byte{80, 78, 71}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Missing x-scale
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.png?y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{80, 78, 71}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// x-scale is not float
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.png?x-scale=Large&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{80, 78, 71}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Aligned-beam-pmc is empty
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=", bytes.NewBuffer([]byte{80, 78, 71}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Bad image type
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.gif?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{80, 78, 71}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Empty image body
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Works
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{80, 78, 71})) // Spells PNG in ascii
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Missing query parameter "aligned-beam-pmc" for matched image: "watson-444.png"

400
Missing query parameter "x-scale" for matched image: "watson-444.png"

400
Query parameter "x-scale" was not a float, for matched image: "watson-444.png"

400
Query parameter "aligned-beam-pmc" was not an int, for matched image: "watson-444.png"

400
Invalid image file type: "watson-444.gif"

400
No image data sent

200
Example (DatasetCustomImagesPost_rgbu)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Expecting uploaded image and JSON file
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/RGBU/nirgbuv.tif"), Body: bytes.NewReader([]byte("TIF")),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

// Bad image type
req, _ := http.NewRequest("POST", "/dataset/images/abc-111/rgbu/nirgbuv.png", bytes.NewBuffer([]byte{84, 73, 70}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// No body
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/rgbu/nirgbuv.tif", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/dataset/images/abc-111/rgbu/nirgbuv.tif", bytes.NewBuffer([]byte{84, 73, 70}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Invalid image file type: "nirgbuv.png"

400
No image data sent

200
Example (DatasetCustomImagesPost_unaligned)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Expecting uploaded image and JSON file
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/UNALIGNED/mastcam.png"), Body: bytes.NewReader([]byte("PNG")),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

// Bad image type
req, _ := http.NewRequest("POST", "/dataset/images/abc-111/unaligned/mastcam.tif", bytes.NewBuffer([]byte{80, 78, 71}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// No body
req, _ = http.NewRequest("POST", "/dataset/images/abc-111/unaligned/mastcam.png", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/dataset/images/abc-111/unaligned/mastcam.png", bytes.NewBuffer([]byte{80, 78, 71}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Invalid image file type: "mastcam.tif"

400
No image data sent

200
Example (DatasetCustomImagesPut)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/MATCHED/doesnt-exist.json"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/MATCHED/watson-444.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "aligned-beam-pmc": 77,
    "matched-image": "watson-444.png",
    "x-offset": 11,
    "y-offset": 12,
    "x-scale": 1.4,
    "y-scale": 1.5
}`))),
	},
}

// Expecting uploaded JSON file ONCE
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-111/MATCHED/watson-444.json"), Body: bytes.NewReader([]byte(`{
    "aligned-beam-pmc": 88,
    "matched-image": "watson-444.png",
    "x-offset": 12,
    "y-offset": 23,
    "x-scale": 1.23,
    "y-scale": 1.1
}`)),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

// Missing aligned-beam-pmc
req, _ := http.NewRequest("PUT", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22", bytes.NewBuffer([]byte{}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Missing x-scale
req, _ = http.NewRequest("PUT", "/dataset/images/abc-111/matched/watson-444.png?y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// x-scale is not float
req, _ = http.NewRequest("PUT", "/dataset/images/abc-111/matched/watson-444.png?x-scale=Large&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Aligned-beam-pmc is empty
req, _ = http.NewRequest("PUT", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Bad image type
req, _ = http.NewRequest("PUT", "/dataset/images/abc-111/unaligned/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Bad image name
req, _ = http.NewRequest("PUT", "/dataset/images/abc-111/matched/doesnt-exist.png?x-scale=1.23&y-scale=1.1&x-offset=11&y-offset=22&aligned-beam-pmc=88", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Works
req, _ = http.NewRequest("PUT", "/dataset/images/abc-111/matched/watson-444.png?x-scale=1.23&y-scale=1.1&x-offset=12&y-offset=23&aligned-beam-pmc=88", bytes.NewBuffer([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Missing query parameter "aligned-beam-pmc" for matched image: "watson-444.png"

400
Missing query parameter "x-scale" for matched image: "watson-444.png"

400
Query parameter "x-scale" was not a float, for matched image: "watson-444.png"

400
Query parameter "aligned-beam-pmc" was not an int, for matched image: "watson-444.png"

400
Invalid custom image type: "unaligned"

404
doesnt-exist.json not found

200
Example (DatasetCustomMetaGet)
const customMetaJSON = `{
"title": "Alien Fossil"
}`
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/custom-meta.json"),
	},
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-456/custom-meta.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(customMetaJSON))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/meta/abc-123", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset/meta/abc-456", nil) // Should return all items. NOTE: tests link creation (though no host name specified so won't have a valid link)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
dataset custom meta not found

200
{
    "title": "Alien Fossil"
}
Example (DatasetCustomMetaPut)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(artifactManualUploadBucket), Key: aws.String("dataset-addons/abc-123/custom-meta.json"), Body: bytes.NewReader([]byte(`{
    "title": "Crater Rim"
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.ManualUploadBucket = artifactManualUploadBucket
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("PUT", "/dataset/meta/abc-123", bytes.NewReader([]byte("{\"title\": \"Crater Rim\"}"))) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

500
AWS Session Not Configured.
Example (DatasetHandler_List)
const datasetsJSON = `{
"datasets": [
  {
   "dataset_id": "590340",
   "title": "the title",
   "site": "the site",
   "target": "the target",
   "group": "groupie",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "",
   "rtt": 590340,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "tiff_context_images": 0,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
  },
  {
   "dataset_id": "983561",
   "group": "the-group",
   "drive_id": 36,
   "site_id": 1,
   "target_id": "?",
   "sol": "",
   "rtt": 983561,
   "sclk": 0,
   "context_image": "MCC-66.png",
   "location_count": 313,
   "data_file_size": 1840596,
   "context_images": 5,
   "tiff_context_images": 0,
   "normal_spectra": 612,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 306,
   "detector_config": "PIXL",
   "create_unixtime_sec": 1234567890
  },
  {
   "dataset_id": "222333",
   "group": "another-group",
   "drive_id": 36,
   "site_id": 1,
   "target_id": "?",
   "sol": "30",
   "rtt": 222333,
   "sclk": 0,
   "context_image": "MCC-66.png",
   "location_count": 313,
   "data_file_size": 1840596,
   "context_images": 5,
   "tiff_context_images": 0,
   "normal_spectra": 612,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 306,
   "detector_config": "PIXL",
   "create_unixtime_sec": 1234567891
  }
]
}`
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/datasets.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(datasetsJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(datasetsJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(datasetsJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(datasetsJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(datasetsJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(datasetsJSON))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Email:  "niko@rockstar.com",
	Permissions: map[string]bool{
		"access:the-group":     true,
		"access:groupie":       true,
		"access:another-group": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset", nil) // Should return all items. NOTE: tests link creation (though no host name specified so won't have a valid link)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Request again with a different user, which excludes groups
delete(mockUser.Permissions, "access:another-group")
fmt.Printf("Permissions left: %v\n", len(mockUser.Permissions))
req, _ = http.NewRequest("GET", "/dataset", nil) // Should return less based on group difference. NOTE: tests link creation (though no host name specified so won't have a valid link)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset?normal_spectra=882&detector_config=PIXL", nil) // Should filter with query string. NOTE: tests link creation (though no host name specified so won't have a valid link)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset?detector_config=Breadboard", nil) // Should return empty list, no items match query
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset?group_id=the-group|another", nil) // Should return item with the-group as its group id
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/dataset?title=he", nil) // Should return the one with title that contains "he" - we only have 1 title set
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
[]

200
[
    {
        "dataset_id": "590340",
        "group": "groupie",
        "drive_id": 292,
        "site_id": 1,
        "target_id": "?",
        "site": "the site",
        "target": "the target",
        "title": "the title",
        "sol": "",
        "rtt": 590340,
        "sclk": 0,
        "context_image": "MCC-234.png",
        "location_count": 446,
        "data_file_size": 2699388,
        "context_images": 1,
        "tiff_context_images": 0,
        "normal_spectra": 882,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 441,
        "detector_config": "PIXL",
        "create_unixtime_sec": 0,
        "dataset_link": "https:///dataset/download/590340/dataset",
        "context_image_link": "https:///dataset/download/590340/MCC-234.png"
    },
    {
        "dataset_id": "983561",
        "group": "the-group",
        "drive_id": 36,
        "site_id": 1,
        "target_id": "?",
        "site": "",
        "target": "",
        "title": "",
        "sol": "",
        "rtt": 983561,
        "sclk": 0,
        "context_image": "MCC-66.png",
        "location_count": 313,
        "data_file_size": 1840596,
        "context_images": 5,
        "tiff_context_images": 0,
        "normal_spectra": 612,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 306,
        "detector_config": "PIXL",
        "create_unixtime_sec": 1234567890,
        "dataset_link": "https:///dataset/download/983561/dataset",
        "context_image_link": "https:///dataset/download/983561/MCC-66.png"
    },
    {
        "dataset_id": "222333",
        "group": "another-group",
        "drive_id": 36,
        "site_id": 1,
        "target_id": "?",
        "site": "",
        "target": "",
        "title": "",
        "sol": "30",
        "rtt": 222333,
        "sclk": 0,
        "context_image": "MCC-66.png",
        "location_count": 313,
        "data_file_size": 1840596,
        "context_images": 5,
        "tiff_context_images": 0,
        "normal_spectra": 612,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 306,
        "detector_config": "PIXL",
        "create_unixtime_sec": 1234567891,
        "dataset_link": "https:///dataset/download/222333/dataset",
        "context_image_link": "https:///dataset/download/222333/MCC-66.png"
    }
]

Permissions left: 2
200
[
    {
        "dataset_id": "590340",
        "group": "groupie",
        "drive_id": 292,
        "site_id": 1,
        "target_id": "?",
        "site": "the site",
        "target": "the target",
        "title": "the title",
        "sol": "",
        "rtt": 590340,
        "sclk": 0,
        "context_image": "MCC-234.png",
        "location_count": 446,
        "data_file_size": 2699388,
        "context_images": 1,
        "tiff_context_images": 0,
        "normal_spectra": 882,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 441,
        "detector_config": "PIXL",
        "create_unixtime_sec": 0,
        "dataset_link": "https:///dataset/download/590340/dataset",
        "context_image_link": "https:///dataset/download/590340/MCC-234.png"
    },
    {
        "dataset_id": "983561",
        "group": "the-group",
        "drive_id": 36,
        "site_id": 1,
        "target_id": "?",
        "site": "",
        "target": "",
        "title": "",
        "sol": "",
        "rtt": 983561,
        "sclk": 0,
        "context_image": "MCC-66.png",
        "location_count": 313,
        "data_file_size": 1840596,
        "context_images": 5,
        "tiff_context_images": 0,
        "normal_spectra": 612,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 306,
        "detector_config": "PIXL",
        "create_unixtime_sec": 1234567890,
        "dataset_link": "https:///dataset/download/983561/dataset",
        "context_image_link": "https:///dataset/download/983561/MCC-66.png"
    }
]

200
[
    {
        "dataset_id": "590340",
        "group": "groupie",
        "drive_id": 292,
        "site_id": 1,
        "target_id": "?",
        "site": "the site",
        "target": "the target",
        "title": "the title",
        "sol": "",
        "rtt": 590340,
        "sclk": 0,
        "context_image": "MCC-234.png",
        "location_count": 446,
        "data_file_size": 2699388,
        "context_images": 1,
        "tiff_context_images": 0,
        "normal_spectra": 882,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 441,
        "detector_config": "PIXL",
        "create_unixtime_sec": 0,
        "dataset_link": "https:///dataset/download/590340/dataset",
        "context_image_link": "https:///dataset/download/590340/MCC-234.png"
    }
]

200
[]

200
[
    {
        "dataset_id": "983561",
        "group": "the-group",
        "drive_id": 36,
        "site_id": 1,
        "target_id": "?",
        "site": "",
        "target": "",
        "title": "",
        "sol": "",
        "rtt": 983561,
        "sclk": 0,
        "context_image": "MCC-66.png",
        "location_count": 313,
        "data_file_size": 1840596,
        "context_images": 5,
        "tiff_context_images": 0,
        "normal_spectra": 612,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 306,
        "detector_config": "PIXL",
        "create_unixtime_sec": 1234567890,
        "dataset_link": "https:///dataset/download/983561/dataset",
        "context_image_link": "https:///dataset/download/983561/MCC-66.png"
    }
]

200
[
    {
        "dataset_id": "590340",
        "group": "groupie",
        "drive_id": 292,
        "site_id": 1,
        "target_id": "?",
        "site": "the site",
        "target": "the target",
        "title": "the title",
        "sol": "",
        "rtt": 590340,
        "sclk": 0,
        "context_image": "MCC-234.png",
        "location_count": 446,
        "data_file_size": 2699388,
        "context_images": 1,
        "tiff_context_images": 0,
        "normal_spectra": 882,
        "dwell_spectra": 0,
        "bulk_spectra": 2,
        "max_spectra": 2,
        "pseudo_intensities": 441,
        "detector_config": "PIXL",
        "create_unixtime_sec": 0,
        "dataset_link": "https:///dataset/download/590340/dataset",
        "context_image_link": "https:///dataset/download/590340/MCC-234.png"
    }
]
Example (DatasetHandler_MCC_Stream_OK)
const summaryJSON = `{
   "dataset_id": "590340",
   "group": "groupie",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "30",
   "rtt": 590340,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "tiff_context_images": 0,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
}`

mccBytes := []byte{60, 112, 110, 103, 62}

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/MCC-234.png"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/MCC-234.png"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/MCC-234.png"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/MCC-455.png"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/indiana-jones.txt"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(mccBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(mccBytes)), // return some printable chars so easier to compare in Output comment
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(mccBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(mccBytes)), // return some printable chars so easier to compare in Output comment
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(mccBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(mccBytes)), // return some printable chars so easier to compare in Output comment
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(mccBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(mccBytes)), // return some printable chars so easier to compare in Output comment
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	"Niko Bellic",
	"600f2a0806b6c70071d3d174",
	"niko@rockstar.com",
	map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

paths := []string{
	"/dataset/download/590340/context-image",
	"/dataset/download/590340/context-thumb",
	"/dataset/download/590340/context-mark-thumb",
	// a different context image
	"/dataset/download/590340/MCC-455.png",
	// non-existant file
	"/dataset/download/590340/indiana-jones.txt",
}

for _, path := range paths {
	req, _ := http.NewRequest("GET", path, nil) // Should return empty list, datasets.json fails to download
	resp := executeRequest(req, apiRouter.Router)

	fmt.Println(resp.Code)
	// Make sure the response is the right kind...
	fmt.Println(resp.HeaderMap["Content-Disposition"])
	fmt.Println(resp.HeaderMap["Content-Length"])
	fmt.Println(resp.Body)
}
Output:

200
[attachment; filename="MCC-234.png"]
[5]
<png>
200
[attachment; filename="MCC-234.png"]
[5]
<png>
200
[attachment; filename="MCC-234.png"]
[5]
<png>
200
[attachment; filename="MCC-455.png"]
[5]
<png>
404
[]
[]
indiana-jones.txt not found
Example (DatasetHandler_Stream_BadGroup_403)
const summaryJSON = `{
   "dataset_id": "590340",
   "group": "groupie",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "0",
   "rtt": 590340,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "tiff_context_images": 0,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
}`
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	"Niko Bellic",
	"600f2a0806b6c70071d3d174",
	"niko@rockstar.com",
	map[string]bool{
		"access:the-group": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/download/590340/dataset", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

403
dataset 590340 not permitted
Example (DatasetHandler_Stream_BadSummary)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte("bad json"))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	"Niko Bellic",
	"600f2a0806b6c70071d3d174",
	"niko@rockstar.com",
	map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/download/590340/dataset", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

500
failed to verify dataset group permission
Example (DatasetHandler_Stream_NoSuchDataset)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	"Niko Bellic",
	"600f2a0806b6c70071d3d174",
	"niko@rockstar.com",
	map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/download/590340/dataset", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
590340 not found
Example (DatasetHandler_Stream_OK)
const summaryJSON = `{
   "dataset_id": "590340",
   "group": "groupie",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "0",
   "rtt": 590340,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "tiff_context_images": 0,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
}`

datasetBytes := []byte{50, 60, 61, 62, 70}

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/dataset.bin"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(datasetBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(datasetBytes)), // return some printable chars so easier to compare in Output comment
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	"Niko Bellic",
	"600f2a0806b6c70071d3d174",
	"niko@rockstar.com",
	map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/dataset/download/590340/dataset", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
// Make sure the response is the right kind...
fmt.Println(resp.HeaderMap["Content-Disposition"])
fmt.Println(resp.HeaderMap["Content-Length"])
fmt.Println(resp.Body)
Output:

200
[attachment; filename="dataset.bin"]
[5]
2<=>F
Example (DetectorConfigHandler_Get)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("DetectorConfig/WeirdDetector/pixlise-config.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("DetectorConfig/PetersSuperDetector/pixlise-config.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"minElement": 11,
	"maxElement": 92,
	"xrfeVLowerBound": 800,
	"xrfeVUpperBound": 20000,
	"xrfeVResolution": 230,
	"windowElement": 4,
	"tubeElement": 14,
	"mmBeamRadius": 0.3,
	"somethingUnknown": 42,
	"defaultParams": "-q,xyzPIELKGHTXCRNFSVM7ijsrdpetoubaln -b,0,8,50 -f"
}`))),
	},
}

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{Bucket: aws.String(ConfigBucketForUnitTest), Prefix: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/")},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/V1/config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/2.0/optic.txt")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/2.0/config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/V1/optic.txt")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/v2.1-broken/file.txt")},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/detector-config/WeirdDetector", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/detector-config/PetersSuperDetector", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
WeirdDetector not found

200
{
    "minElement": 11,
    "maxElement": 92,
    "xrfeVLowerBound": 800,
    "xrfeVUpperBound": 20000,
    "xrfeVResolution": 230,
    "windowElement": 4,
    "tubeElement": 14,
    "defaultParams": "-q,xyzPIELKGHTXCRNFSVM7ijsrdpetoubaln -b,0,8,50 -f",
    "mmBeamRadius": 0.3,
    "piquantConfigVersions": [
        "V1",
        "2.0"
    ]
}
Example (DetectorConfigHandler_OtherMethods)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/detector-config/WeirdDetector", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("PUT", "/detector-config/WeirdDetector", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("DELETE", "/detector-config/WeirdDetector", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405

405

405
Example (DetectorQuantConfigHandler_GetNotFound)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("DetectorConfig/WeirdDetector/pixlise-config.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/piquant/config/WeirdDetector/version/v1.1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
WeirdDetector not found
Example (DetectorQuantConfigHandler_GetOK)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("DetectorConfig/PetersSuperDetector/pixlise-config.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/ver1.1/config.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"minElement": 11,
	"maxElement": 92,
	"xrfeVLowerBound": 800,
	"xrfeVUpperBound": 20000,
	"xrfeVResolution": 230,
	"windowElement": 4,
	"tubeElement": 14,
	"somethingUnknown": 42,
	"defaultParams": "-q,xyzPIELKGHTXCRNFSVM7ijsrdpetoubaln -b,0,8,50 -f",
	"mmBeamRadius": 0.007
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"description": "Peters super detector config",
	"config-file": "config.msa",
	"optic-efficiency": "optical.csv",
	"calibration-file": "calibration.csv",
	"standards-file": "standards.csv"
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/piquant/config/PetersSuperDetector/version/ver1.1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "pixliseConfig": {
        "minElement": 11,
        "maxElement": 92,
        "xrfeVLowerBound": 800,
        "xrfeVUpperBound": 20000,
        "xrfeVResolution": 230,
        "windowElement": 4,
        "tubeElement": 14,
        "defaultParams": "-q,xyzPIELKGHTXCRNFSVM7ijsrdpetoubaln -b,0,8,50 -f",
        "mmBeamRadius": 0.007
    },
    "quantConfig": {
        "description": "Peters super detector config",
        "configFile": "config.msa",
        "opticEfficiencyFile": "optical.csv",
        "calibrationFile": "calibration.csv",
        "standardsFile": "standards.csv"
    }
}
Example (DetectorQuantConfigHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{Bucket: aws.String(ConfigBucketForUnitTest), Prefix: aws.String("DetectorConfig/")},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("DetectorConfig/PetersSuperDetector/pixlise-config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/V1/config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/2.0/optic.txt")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/2.0/config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/V1/optic.txt")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/v2.1-broken/file.txt")},
			{Key: aws.String("DetectorConfig/AnotherConfig/pixlise-config.json")},
			{Key: aws.String("DetectorConfig/AnotherConfig/PiquantConfigs/V1/config.json")},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/piquant/config", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Returns config names in alphabetical order
Output:

200
{
    "configNames": [
        "AnotherConfig",
        "PetersSuperDetector"
    ]
}
Example (DetectorQuantConfigHandler_OtherMethods)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/piquant/config/WeirdDetector/version/1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("PUT", "/piquant/config/WeirdDetector/version/1", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("DELETE", "/piquant/config/WeirdDetector/version/1", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405

405

405
Example (DetectorQuantConfigHandler_VersionList)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{Bucket: aws.String(ConfigBucketForUnitTest), Prefix: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/")},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/V1/config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/2.0/optic.txt")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/2.0/config.json")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/V1/optic.txt")},
			{Key: aws.String("DetectorConfig/PetersSuperDetector/PiquantConfigs/v2.1-broken/file.txt")},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/piquant/config/PetersSuperDetector/versions", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
[
    "V1",
    "2.0"
]
Example (DiffractionHandler_Delete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`garbage`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(statusFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(statusFile))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path), Body: bytes.NewReader([]byte(`{
    "id-1": "not-anomaly"
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// File missing, 404
req, _ := http.NewRequest("DELETE", "/diffraction/status/rtt-123/new-1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Incoming is garbage, 500
req, _ = http.NewRequest("DELETE", "/diffraction/status/rtt-123/new-2", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Not found in list
req, _ = http.NewRequest("DELETE", "/diffraction/status/rtt-123/new-3", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// OK
req, _ = http.NewRequest("DELETE", "/diffraction/status/rtt-123/id-2", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
new-1 not found

500
invalid character 'g' looking for beginning of value

404
new-3 not found

200
{
    "id-1": "not-anomaly"
}
Example (DiffractionHandler_DeleteManual)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`garbage`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(manualFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(manualFile))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path), Body: bytes.NewReader([]byte(`{
    "peaks": {
        "id-1": {
            "pmc": 32,
            "keV": 5.6
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// File missing, 404
req, _ := http.NewRequest("DELETE", "/diffraction/manual/rtt-123/new-1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Incoming is garbage, 500
req, _ = http.NewRequest("DELETE", "/diffraction/manual/rtt-123/new-2", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Not found in list
req, _ = http.NewRequest("DELETE", "/diffraction/manual/rtt-123/new-3", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// OK
req, _ = http.NewRequest("DELETE", "/diffraction/manual/rtt-123/id-2", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
new-1 not found

500
invalid character 'g' looking for beginning of value

404
new-3 not found

200
{
    "id-1": {
        "pmc": 32,
        "keV": 5.6
    }
}
Example (DiffractionHandler_ListAccepted)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`garbage`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(statusFile))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/diffraction/status/rtt-123", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/diffraction/status/rtt-123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/diffraction/status/rtt-123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{}

500
invalid character 'g' looking for beginning of value

200
{
    "id-1": "not-anomaly",
    "id-2": "intensity-mismatch"
}
Example (DiffractionHandler_ListManual)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`garbage`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(manualFile))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/diffraction/manual/rtt-123", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/diffraction/manual/rtt-123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/diffraction/manual/rtt-123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{}

500
invalid character 'g' looking for beginning of value

200
{
    "id-1": {
        "pmc": 32,
        "keV": 5.6
    },
    "id-2": {
        "pmc": 44,
        "keV": 7.7
    }
}
Example (DiffractionHandler_PostManual)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`garbage`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(manualFile))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path), Body: bytes.NewReader([]byte(`{
    "peaks": {
        "new1": {
            "pmc": 35,
            "keV": 5.5
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path), Body: bytes.NewReader([]byte(`{
    "peaks": {
        "new2": {
            "pmc": 35,
            "keV": 5.5
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(manualS3Path), Body: bytes.NewReader([]byte(`{
    "peaks": {
        "id-1": {
            "pmc": 32,
            "keV": 5.6
        },
        "id-2": {
            "pmc": 44,
            "keV": 7.7
        },
        "new3": {
            "pmc": 35,
            "keV": 5.5
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"new1", "new2", "new3", "new4"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

postItem := `{
	"pmc": 35,
	"keV": 5.5
}`

// File missing, first go, should just create
req, _ := http.NewRequest("POST", "/diffraction/manual/rtt-123", bytes.NewReader([]byte(postItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Should ignore the fact that the incoming file is garbage, and write a new one
req, _ = http.NewRequest("POST", "/diffraction/manual/rtt-123", bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// New appended to existing list
req, _ = http.NewRequest("POST", "/diffraction/manual/rtt-123", bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "new1": {
        "pmc": 35,
        "keV": 5.5
    }
}

200
{
    "new2": {
        "pmc": 35,
        "keV": 5.5
    }
}

200
{
    "id-1": {
        "pmc": 32,
        "keV": 5.6
    },
    "id-2": {
        "pmc": 44,
        "keV": 7.7
    },
    "new3": {
        "pmc": 35,
        "keV": 5.5
    }
}
Example (DiffractionHandler_PostStatuses)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`garbage`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(statusFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(statusFile))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path), Body: bytes.NewReader([]byte(`{
    "new-1": "diffraction"
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path), Body: bytes.NewReader([]byte(`{
    "new-2": "other"
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path), Body: bytes.NewReader([]byte(`{
    "id-1": "not-anomaly",
    "id-2": "intensity-mismatch",
    "new-3": "weird-one"
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(statusS3Path), Body: bytes.NewReader([]byte(`{
    "id-1": "not-anomaly",
    "id-2": "not-anomaly"
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// File missing, first go, should just create
req, _ := http.NewRequest("POST", "/diffraction/status/diffraction/rtt-123/new-1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Should ignore the fact that the incoming file is garbage, and write a new one
req, _ = http.NewRequest("POST", "/diffraction/status/other/rtt-123/new-2", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// New appended to existing list
req, _ = http.NewRequest("POST", "/diffraction/status/weird-one/rtt-123/new-3", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Checking no duplicates made
req, _ = http.NewRequest("POST", "/diffraction/status/not-anomaly/rtt-123/id-2", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "new-1": "diffraction"
}

200
{
    "new-2": "other"
}

200
{
    "id-1": "not-anomaly",
    "id-2": "intensity-mismatch",
    "new-3": "weird-one"
}

200
{
    "id-1": "not-anomaly",
    "id-2": "not-anomaly"
}
Example (ElementSetHandler_Delete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "55": {
        "name": "The shared item",
        "lines": [
            {
                "Z": 13,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": ""
        }
    }
}`))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path), Body: bytes.NewReader([]byte(`{
    "44": {
        "name": "My Tuesday Elements",
        "lines": [
            {
                "Z": 13,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            },
            {
                "Z": 14,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    }
}`)),
	},
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path), Body: bytes.NewReader([]byte(`{}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
	&s3.PutObjectOutput{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Delete finds file missing, ERROR
req, _ := http.NewRequest("DELETE", "/element-set/13", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds empty file, ERROR
req, _ = http.NewRequest("DELETE", "/element-set/13", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete cant find item, ERROR
req, _ = http.NewRequest("DELETE", "/element-set/15", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds item, OK
req, _ = http.NewRequest("DELETE", "/element-set/13", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item but from wrong user, ERROR
req, _ = http.NewRequest("DELETE", "/element-set/shared-13", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item, OK
req, _ = http.NewRequest("DELETE", "/element-set/shared-55", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
13 not found

404
13 not found

404
15 not found

200

401
13 not owned by 600f2a0806b6c70071d3d174

200
Example (ElementSetHandler_Get)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// File not in S3, should return 404
req, _ := http.NewRequest("GET", "/element-set/13", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File in S3 empty, should return 404
req, _ = http.NewRequest("GET", "/element-set/13", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains stuff, using ID thats not in there, should return 404
req, _ = http.NewRequest("GET", "/element-set/15", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains stuff, using ID that exists
req, _ = http.NewRequest("GET", "/element-set/13", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Check that shared file was loaded if shared ID sent in
req, _ = http.NewRequest("GET", "/element-set/shared-13", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
13 not found

404
13 not found

404
15 not found

200
{
    "name": "My Monday Elements",
    "lines": [
        {
            "Z": 26,
            "K": true,
            "L": true,
            "M": false,
            "Esc": false
        },
        {
            "Z": 20,
            "K": true,
            "L": false,
            "M": false,
            "Esc": false
        }
    ],
    "shared": false,
    "creator": {
        "name": "Peter",
        "user_id": "u123",
        "email": ""
    }
}

200
{
    "name": "My Monday Elements",
    "lines": [
        {
            "Z": 26,
            "K": true,
            "L": true,
            "M": false,
            "Esc": false
        },
        {
            "Z": 20,
            "K": true,
            "L": false,
            "M": false,
            "Esc": false
        }
    ],
    "shared": true,
    "creator": {
        "name": "Peter",
        "user_id": "u123",
        "email": ""
    }
}
Example (ElementSetHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"13": {
		"name": "My Monday Elements",
		"lines": [
			{
				"Z": 26,
				"K": true,
				"L": true,
				"M": false,
				"Esc": false
			},
			{
				"Z": 20,
				"K": true,
				"L": false,
				"M": false,
				"Esc": false
			}
		],
		"creator": { "name": "Peter", "user_id": "u123" }
	}
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"88": {
		"name": "Shared Elements",
		"lines": [
			{
				"Z": 32,
				"K": true,
				"L": true,
				"M": false,
				"Esc": false
			}
		],
		"creator": { "name": "Mike", "user_id": "u125" }
	}
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/element-set", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/element-set", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/element-set", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{}

200
{}

200
{
    "13": {
        "name": "My Monday Elements",
        "atomicNumbers": [
            26,
            20
        ],
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": ""
        }
    },
    "shared-88": {
        "name": "Shared Elements",
        "atomicNumbers": [
            32
        ],
        "shared": true,
        "creator": {
            "name": "Mike",
            "user_id": "u125",
            "email": ""
        }
    }
}
Example (ElementSetHandler_Post)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path), Body: bytes.NewReader([]byte(`{
    "55": {
        "name": "Latest set",
        "lines": [
            {
                "Z": 43,
                "K": true,
                "L": true,
                "M": true,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path), Body: bytes.NewReader([]byte(`{
    "56": {
        "name": "Latest set",
        "lines": [
            {
                "Z": 43,
                "K": true,
                "L": true,
                "M": true,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path), Body: bytes.NewReader([]byte(`{
    "13": {
        "name": "My Monday Elements",
        "lines": [
            {
                "Z": 26,
                "K": true,
                "L": true,
                "M": false,
                "Esc": false
            },
            {
                "Z": 20,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": ""
        }
    },
    "44": {
        "name": "My Tuesday Elements",
        "lines": [
            {
                "Z": 13,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            },
            {
                "Z": 14,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    },
    "57": {
        "name": "Latest set",
        "lines": [
            {
                "Z": 43,
                "K": true,
                "L": true,
                "M": true,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
	&s3.PutObjectOutput{},
	&s3.PutObjectOutput{},
}

var idGen MockIDGenerator
idGen.ids = []string{"55", "56", "57"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const postItem = `{
	"name": "Latest set",
	"lines": [
		{
			"Z": 43,
			"K": true,
			"L": true,
			"M": true,
			"Esc": false
		}
	]
}`

// File not in S3, should work
req, _ := http.NewRequest("POST", "/element-set", bytes.NewReader([]byte(postItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should work
req, _ = http.NewRequest("POST", "/element-set", bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File doesn't contain item by this name, should work (add)
req, _ = http.NewRequest("POST", "/element-set", bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200

200

200
Example (ElementSetHandler_Put)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path), Body: bytes.NewReader([]byte(`{
    "13": {
        "name": "My Monday Elements",
        "lines": [
            {
                "Z": 26,
                "K": true,
                "L": true,
                "M": false,
                "Esc": false
            },
            {
                "Z": 20,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": ""
        }
    },
    "44": {
        "name": "Latest set",
        "lines": [
            {
                "Z": 43,
                "K": true,
                "L": true,
                "M": true,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
		"name": "Latest set",
		"lines": [
			{
				"Z": 43,
				"K": true,
				"L": true,
				"M": true,
				"Esc": false
			}
		]
	}`

// File not in S3, should say not found
req, _ := http.NewRequest("PUT", "/element-set/44", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("PUT", "/element-set/44", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File already contains this id, should overwrite
req, _ = http.NewRequest("PUT", "/element-set/44", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File doesn't contain this id, should say not found
req, _ = http.NewRequest("PUT", "/element-set/59", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Can't edit shared ids
req, _ = http.NewRequest("PUT", "/element-set/shared-59", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
44 not found

404
44 not found

200

404
59 not found

400
Cannot edit shared items
Example (ElementSetHandler_Share)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(elemUserS3Path),
	},
	// Reading shared file to add to it
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/ElementSets.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(elemFile))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "55": {
        "name": "Already shared item",
        "lines": [
            {
                "Z": 13,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174"
        }
    }
}`))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/ElementSets.json"), Body: bytes.NewReader([]byte(`{
    "55": {
        "name": "Already shared item",
        "lines": [
            {
                "Z": 13,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "shared": false,
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": ""
        }
    },
    "77": {
        "name": "My Tuesday Elements",
        "lines": [
            {
                "Z": 13,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            },
            {
                "Z": 14,
                "K": true,
                "L": false,
                "M": false,
                "Esc": false
            }
        ],
        "shared": true,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
}

var idGen MockIDGenerator
idGen.ids = []string{"77"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = ""

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/element-set/33", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("POST", "/share/element-set/33", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File missing the id being shared
req, _ = http.NewRequest("POST", "/share/element-set/33", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains ID, share OK
req, _ = http.NewRequest("POST", "/share/element-set/44", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
33 not found

404
33 not found

404
33 not found

200
"77"
Example (Hints)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(hintJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(hintJSON))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"), Body: bytes.NewReader([]byte(hintJSON)),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)

apiRouter := MakeRouter(svcs)

jsonstr := `{
				"hints": [      
					"hint c",
					"hint d"
				]}`
req, _ := http.NewRequest("POST", "/notification/hints", bytes.NewReader([]byte(jsonstr)))
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)

req, _ = http.NewRequest("GET", "/notification/hints", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)
Output:

ensure-valid: 200
{
    "hints": [
        "hint c",
        "hint d"
    ]
}
ensure-valid: 200
{
    "hints": [
        "hint c",
        "hint d"
    ]
}
Example (Hints_empty)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// User requesting their hints
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}

// We're saying there's no hint file in S3
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

// Expect API to upload a hints file
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"), Body: bytes.NewReader([]byte(emptyUserJSON)),
	},
}

// Mocking empty OK response from put call
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)

apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/notification/hints", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)
Output:

ensure-valid: 200
{
    "hints": []
}
Example (IsABQuant)
fmt.Printf("%v\n", isABQuant([]string{
	"header",
	"PMC,filename,Ca_%,Ca_err",
	"100,Normal_A,1.2,1.1",
	"100,Normal_B,1.4,1.2",
	"101,Normal_A,1.3,1.2",
	"101,Normal_B,1.6,1.6",
}, 1))

fmt.Printf("%v\n", isABQuant([]string{
	"header",
	"PMC,filename,Ca_%,Ca_err",
	"100,Normal_A,1.2,1.1",
	"101,Normal_A,1.3,1.2",
	"100,Normal_B,1.4,1.2",
	"101,Normal_B,1.6,1.6",
}, 1))

fmt.Printf("%v\n", isABQuant([]string{
	"header",
	"PMC,filename,Ca_%,Ca_err",
	"100,Normal_Combined,1.2,1.1",
	"101,Normal_Combined,1.3,1.2",
}, 1))
Output:

true
true
false
Example (MatchesSearch)
ds := datasetModel.SummaryFileData{
	DatasetID:         "590340",
	Group:             "the-group",
	DriveID:           292,
	SiteID:            1,
	TargetID:          "?",
	SOL:               "10",
	RTT:               590340,
	SCLK:              123456,
	ContextImage:      "MCC-234.png",
	LocationCount:     446,
	DataFileSize:      2699388,
	ContextImages:     1,
	NormalSpectra:     882,
	DwellSpectra:      0,
	BulkSpectra:       2,
	MaxSpectra:        2,
	PseudoIntensities: 441,
	DetectorConfig:    "PIXL",
}

queryItems := [][]queryItem{
	[]queryItem{queryItem{"location_count", "=", "446"}},
	[]queryItem{queryItem{"location_count", "=", "445"}},
	[]queryItem{queryItem{"location_count", ">", "445"}},
	[]queryItem{queryItem{"location_count", "<", "500"}},
	[]queryItem{queryItem{"dataset_id", ">", "590300"}},
	[]queryItem{queryItem{"sol", ">", "7"}, queryItem{"sol", "<", "141"}},
	[]queryItem{queryItem{"location_count", "=", "446"}, queryItem{"detector_config", "=", "PIXL"}},
	[]queryItem{
		queryItem{"location_count", "=", "446"},
		queryItem{"sol", "=", "10"},
		queryItem{"rtt", "<", "600000"},
		queryItem{"sclk", ">", "123450"},
		queryItem{"data_file_size", ">", "2600000"},
		queryItem{"normal_spectra", ">", "800"},
		queryItem{"drive_id", ">", "290"},
		queryItem{"site_id", "=", "1"},
	},
	[]queryItem{
		queryItem{"location_count", "=", "446"},
		queryItem{"sol", "=", "10"},
		queryItem{"rtt", "<", "600000"},
		queryItem{"sclk", ">", "123450"},
		queryItem{"data_file_size", ">", "2600000"},
		queryItem{"normal_spectra", ">", "800"},
		queryItem{"drive_id", ">", "292"},
		queryItem{"site_id", "=", "1"},
	},
	[]queryItem{},
	[]queryItem{queryItem{"group_id", "=", "group1|the-group|anotherone"}},
	[]queryItem{queryItem{"group_id", "=", "group1|not-the-group|anotherone"}},
}

for _, q := range queryItems {
	match, err := matchesSearch(q, ds)
	fmt.Printf("%v|%v\n", err, match)
}
Output:

<nil>|true
<nil>|false
<nil>|true
<nil>|true
Failed to compare dataset_id, can only use = for values "590300", "590340"|false
<nil>|true
<nil>|true
<nil>|true
<nil>|false
<nil>|true
<nil>|true
<nil>|false
Example (ParseQueryParams)
params := []map[string]string{
	map[string]string{"unknown": "field"},
	map[string]string{"location_count": "30"},
	map[string]string{"location_count": "lt|550"},
	map[string]string{"location_count": "gt|1234"},
	map[string]string{"location_count": "bw|10|33"},
	map[string]string{"dataset_id": "30"},
	map[string]string{"dataset_id": "lt|30"},
	map[string]string{"dataset_id": "30", "whatever": "field", "location_count": "gt|1234"},
	map[string]string{"dataset_id": "30", "location_count": "gt|1234", "whatever": "value"},
}

for _, v := range params {
	q, err := parseQueryParams(v)
	fmt.Printf("%v|%v\n", err, q)
}
Output:

Search not permitted on field: unknown|[]
<nil>|[{location_count = 30}]
<nil>|[{location_count < 550}]
<nil>|[{location_count > 1234}]
<nil>|[{location_count > 10} {location_count < 33}]
<nil>|[{dataset_id = 30}]
<nil>|[{dataset_id < 30}]
Search not permitted on field: whatever|[]
Search not permitted on field: whatever|[]
Example (PiquantDownloadHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
const artifactBucket = "our-artifact-bucket"

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(artifactBucket), Prefix: aws.String("piquant/"),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("piquant/invalid-path.txt"), LastModified: aws.Time(time.Unix(1597124080, 0)), Size: aws.Int64(12)},
			{Key: aws.String("piquant/piquant-linux-2.7.1.zip"), LastModified: aws.Time(time.Unix(1597124080, 0)), Size: aws.Int64(1234)},
			{Key: aws.String("piquant/piquant-windows-2.6.0.zip"), LastModified: aws.Time(time.Unix(1597124000, 0)), Size: aws.Int64(12345)},
		},
	},
}

var mockSigner awsutil.MockSigner
mockSigner.Urls = []string{"http://signed-url.com/file1.zip", "http://signed-url.com/file2.zip"}

svcs := MakeMockSvcs(&mockS3, nil, &mockSigner, nil, nil)
svcs.Config.BuildsBucket = artifactBucket

apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/piquant/download", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "downloadItems": [
        {
            "buildVersion": "2.7.1",
            "buildDateUnixSec": 1597124080,
            "fileName": "piquant-linux-2.7.1.zip",
            "fileSizeBytes": 1234,
            "downloadUrl": "http://signed-url.com/file1.zip",
            "os": "linux"
        },
        {
            "buildVersion": "2.6.0",
            "buildDateUnixSec": 1597124000,
            "fileName": "piquant-windows-2.6.0.zip",
            "fileSizeBytes": 12345,
            "downloadUrl": "http://signed-url.com/file2.zip",
            "os": "windows"
        }
    ]
}
Example (PiquantDownloadHandler_OtherMethods)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/piquant/download", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("PUT", "/piquant/download", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("DELETE", "/piquant/download", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/piquant/download/some-id", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405

405

405

404
404 page not found
Example (PiquantHandler_GetVersion)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/piquant-version.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/piquant-version.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "version": "registry.github.com/pixlise/piquant/runner:3.0.7-ALPHA",
    "changedUnixTimeSec": 1234567890,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Config.PiquantDockerImage = "registry.github.com/pixlise/piquant/runner:3.0.8-ALPHA"
apiRouter := MakeRouter(svcs)

// Success, we have config var set, returns that
req, _ := http.NewRequest("GET", "/piquant/version", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Success, S3 overrides config var
req, _ = http.NewRequest("GET", "/piquant/version", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:


200
{
    "version": "registry.github.com/pixlise/piquant/runner:3.0.8-ALPHA",
    "changedUnixTimeSec": 0,
    "creator": {
        "name": "",
        "user_id": "",
        "email": ""
    }
}

200
{
    "version": "registry.github.com/pixlise/piquant/runner:3.0.7-ALPHA",
    "changedUnixTimeSec": 1234567890,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}
Example (PiquantHandler_GetVersion_NoConfigVar)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/piquant-version.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Fails, no S3 and no config var
req, _ := http.NewRequest("GET", "/piquant/version", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
PIQUANT version not found
Example (PiquantHandler_SetVersion)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

//fmt.Println(string(expBinBytes))
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/piquant-version.json"), Body: bytes.NewReader([]byte(`{
    "version": "registry.github.com/pixlise/piquant/runner:3.0.7-ALPHA",
    "changedUnixTimeSec": 1234567777,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{1234567777},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/piquant/version", bytes.NewReader([]byte(`{
    "version": "registry.github.com/pixlise/piquant/runner:3.0.7-ALPHA"
}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (QuantHandler_AdminList)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing lists files in user dir, share dir, then gets jobs.json and forms a single list of job summaries to send back
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(jobBucketForUnitTest), Prefix: aws.String("JobSummaries/"),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("JobSummaries/rtt-456-jobs.json")},
			{Key: aws.String("JobSummaries/rtt-456/shouldnt/behere.pmcs")},
			{Key: aws.String("JobSummaries/rtt-456-tmp.txt")},
			{Key: aws.String("JobSummaries/rtt-111-jobs.json")},
			{Key: aws.String("JobSummaries/rtt-222-jobs.json")},
			{Key: aws.String("JobSummaries/rtt-123-jobs.json")},
			{Key: aws.String("JobSummaries/rtt-123/job2/params.json")},
			{Key: aws.String("JobSummaries/jobs.json")},
		},
	},
}

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobSummaries/rtt-456-jobs.json"),
	},
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobSummaries/rtt-111-jobs.json"),
	},
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobSummaries/rtt-222-jobs.json"),
	},
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobSummaries/rtt-123-jobs.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "job1": {
        "shared": false,
        "params": {
            "pmcsCount": 313,
            "name": "alllll",
            "dataBucket": "dev-pixlise-data",
            "datasetPath": "Datasets/rtt-456/dataset.bin",
            "datasetID": "rtt-456",
            "jobBucket": "dev-pixlise-piquant-jobs",
            "detectorConfig": "PIXL",
            "elements": [
                "Sc",
                "Cr"
            ],
            "parameters": "-q,pPIETXCFsr -b,0,12,60,910,2800,16",
            "runTimeSec": 60,
            "coresPerNode": 6,
            "startUnixTime": 1592968112,
            "creator": {
                "name": "peternemere",
                "user_id": "5de45d85ca40070f421a3a34",
                "email": ""
            },
            "roiID": "",
            "elementSetID": "",
            "piquantVersion": "3.0.3",
            "quantMode": "Combined"
        },
        "elements": [
            "Sc",
            "Cr"
        ],
        "jobId": "job1",
        "status": "error",
        "message": "Error when converting combined CSV to PIXLISE bin format: Failed to determine detector ID from filename column: ",
        "endUnixTime": 1592968196,
        "outputFilePath": "",
        "piquantLogList": null
    },
    "job2": {
        "shared": false,
        "params": {
            "pmcsCount": 1769,
            "name": "ase12",
            "dataBucket": "dev-pixlise-data",
            "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
            "datasetID": "rtt-456",
            "jobBucket": "dev-pixlise-piquant-jobs",
            "detectorConfig": "PIXL",
            "elements": [
                "Ru",
                "Cr"
            ],
            "parameters": "-q,pPIETXCFsr -b,0,12,60,910,2800,16",
            "runTimeSec": 120,
            "coresPerNode": 6,
            "startUnixTime": 1591826489,
            "creator": {
                "name": "tom",
                "user_id": "5e3b3bc480ee5c191714d6b7",
                "email": ""
            },
            "roiID": "",
            "elementSetID": "",
            "quantMode": "AB"
        },
        "jobId": "job2",
        "status": "nodes_running",
        "message": "Node count: 1, Files/Node: 1770",
        "endUnixTime": 0,
        "outputFilePath": "",
        "piquantLogList": null
    }
}`))),
	},
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`bad json`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"job7": {
        "shared": false,
		"params": {
			"pmcsCount": 93,
			"name": "in progress quant",
			"dataBucket": "dev-pixlise-data",
			"datasetPath": "Datasets/rtt-123/5x5dataset.bin",
			"datasetID": "rtt-123",
			"jobBucket": "dev-pixlise-piquant-jobs",
			"detectorConfig": "PIXL",
			"elements": [
				"Sc",
				"Cr"
			],
			"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
			"runTimeSec": 120,
			"coresPerNode": 6,
			"startUnixTime": 1589948988,
			"creator": {
				"name": "peternemere",
				"user_id": "600f2a0806b6c70071d3d174",
                "email": ""
			},
			"roiID": "ZcH49SYZ",
			"elementSetID": ""
		},
		"jobId": "job7",
		"status": "complete",
		"message": "Something about the nodes",
        "endUnixTime": 1592968196,
        "outputFilePath": "",
        "piquantLogList": null
	}
}`))),
	},
}
// NOTE: job2 and job7 was missing elements, because we introduced this later, checking that API still puts in empty list always

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/quantification", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)

// Order of response items was unpredictable, print them in alphabetical id order
respSummaries := []quantModel.JobSummaryItem{}

getBody, _ := ioutil.ReadAll(resp.Body)
fmt.Println(json.Unmarshal(getBody, &respSummaries))

keys := []string{"job1", "job2", "job7"}
for _, key := range keys {
	for _, item := range respSummaries {
		if item.JobID == key {
			itemJ, _ := json.MarshalIndent(item, "", utils.PrettyPrintIndentForJSON)
			fmt.Println(string(itemJ))
		}
	}
}
Output:

200
<nil>
{
    "shared": false,
    "params": {
        "pmcsCount": 313,
        "name": "alllll",
        "dataBucket": "dev-pixlise-data",
        "datasetPath": "Datasets/rtt-456/dataset.bin",
        "datasetID": "rtt-456",
        "jobBucket": "dev-pixlise-piquant-jobs",
        "detectorConfig": "PIXL",
        "elements": [
            "Sc",
            "Cr"
        ],
        "parameters": "-q,pPIETXCFsr -b,0,12,60,910,2800,16",
        "runTimeSec": 60,
        "coresPerNode": 6,
        "startUnixTime": 1592968112,
        "creator": {
            "name": "peternemere",
            "user_id": "5de45d85ca40070f421a3a34",
            "email": ""
        },
        "roiID": "",
        "elementSetID": "",
        "piquantVersion": "3.0.3",
        "quantMode": "Combined",
        "comments": "",
        "roiIDs": []
    },
    "elements": [
        "Sc",
        "Cr"
    ],
    "jobId": "job1",
    "status": "error",
    "message": "Error when converting combined CSV to PIXLISE bin format: Failed to determine detector ID from filename column: ",
    "endUnixTime": 1592968196,
    "outputFilePath": "",
    "piquantLogList": null
}
{
    "shared": false,
    "params": {
        "pmcsCount": 1769,
        "name": "ase12",
        "dataBucket": "dev-pixlise-data",
        "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
        "datasetID": "rtt-456",
        "jobBucket": "dev-pixlise-piquant-jobs",
        "detectorConfig": "PIXL",
        "elements": [
            "Ru",
            "Cr"
        ],
        "parameters": "-q,pPIETXCFsr -b,0,12,60,910,2800,16",
        "runTimeSec": 120,
        "coresPerNode": 6,
        "startUnixTime": 1591826489,
        "creator": {
            "name": "tom",
            "user_id": "5e3b3bc480ee5c191714d6b7",
            "email": ""
        },
        "roiID": "",
        "elementSetID": "",
        "piquantVersion": "",
        "quantMode": "AB",
        "comments": "",
        "roiIDs": []
    },
    "elements": [],
    "jobId": "job2",
    "status": "nodes_running",
    "message": "Node count: 1, Files/Node: 1770",
    "endUnixTime": 0,
    "outputFilePath": "",
    "piquantLogList": null
}
{
    "shared": false,
    "params": {
        "pmcsCount": 93,
        "name": "in progress quant",
        "dataBucket": "dev-pixlise-data",
        "datasetPath": "Datasets/rtt-123/5x5dataset.bin",
        "datasetID": "rtt-123",
        "jobBucket": "dev-pixlise-piquant-jobs",
        "detectorConfig": "PIXL",
        "elements": [
            "Sc",
            "Cr"
        ],
        "parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
        "runTimeSec": 120,
        "coresPerNode": 6,
        "startUnixTime": 1589948988,
        "creator": {
            "name": "peternemere",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": ""
        },
        "roiID": "ZcH49SYZ",
        "elementSetID": "",
        "piquantVersion": "",
        "quantMode": "",
        "comments": "",
        "roiIDs": []
    },
    "elements": [],
    "jobId": "job7",
    "status": "complete",
    "message": "Something about the nodes",
    "endUnixTime": 1592968196,
    "outputFilePath": "",
    "piquantLogList": null
}
Example (QuantHandler_BlessShared1stQuant)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Getting the quant summary to verify it exists
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
	nil,
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"), Body: bytes.NewReader([]byte(`{
    "history": [
        {
            "version": 1,
            "blessedAt": 1234567890,
            "userId": "600f2a0806b6c70071d3d174",
            "userName": "Niko Bellic",
            "jobId": "job2"
        }
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{1234567890},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/bless/rtt-456/shared-job2", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (QuantHandler_BlessSharedQuantV4)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Getting the quant summary to verify it exists
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"history": [
		{
			"version": 1,
			"blessedAt": 1234567090,
			"userId": "600f2a0806b6c70071d3d174",
			"userName": "Niko Bellic",
			"jobId": "jobAAA"
		},
		{
			"version": 3,
			"blessedAt": 1234567690,
			"userId": "555555",
			"userName": "Michael Da Santa",
			"jobId": "jobCCC"
		},
		{
			"version": 2,
			"blessedAt": 1234567490,
			"userId": "600f2a0806b6c70071d3d174",
			"userName": "Niko Bellic",
			"jobId": "jobBBB"
		}
	]
}`))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"), Body: bytes.NewReader([]byte(`{
    "history": [
        {
            "version": 1,
            "blessedAt": 1234567090,
            "userId": "600f2a0806b6c70071d3d174",
            "userName": "Niko Bellic",
            "jobId": "jobAAA"
        },
        {
            "version": 3,
            "blessedAt": 1234567690,
            "userId": "555555",
            "userName": "Michael Da Santa",
            "jobId": "jobCCC"
        },
        {
            "version": 2,
            "blessedAt": 1234567490,
            "userId": "600f2a0806b6c70071d3d174",
            "userName": "Niko Bellic",
            "jobId": "jobBBB"
        },
        {
            "version": 4,
            "blessedAt": 1234567890,
            "userId": "600f2a0806b6c70071d3d174",
            "userName": "Niko Bellic",
            "jobId": "job2"
        }
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{1234567890},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/bless/rtt-456/shared-job2", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (QuantHandler_BlessUserQuant)

This is different, because it has to implicitly do a share of the quantification!

rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Getting the quant summary to verify it exists
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		// Share verifies that summary exists
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		// Post sharing, bless checks that shared summary exists
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		// Getting previous blessed list
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
	}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": true,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
	nil,
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"), Body: bytes.NewReader([]byte(`{
    "history": [
        {
            "version": 1,
            "blessedAt": 1234567890,
            "userId": "600f2a0806b6c70071d3d174",
            "userName": "Niko Bellic",
            "jobId": "job2"
        }
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

// Copying object (the sharing operation)
mockS3.ExpCopyObjectInput = []s3.CopyObjectInput{
	{
		Bucket:     aws.String(UsersBucketForUnitTest),
		Key:        aws.String("UserContent/shared/rtt-456/Quantifications/job2.bin"),
		CopySource: aws.String(UsersBucketForUnitTest + "/UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2.bin"),
	},
	{
		Bucket:     aws.String(UsersBucketForUnitTest),
		Key:        aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json"),
		CopySource: aws.String(UsersBucketForUnitTest + "/UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		Bucket:     aws.String(UsersBucketForUnitTest),
		Key:        aws.String("UserContent/shared/rtt-456/Quantifications/job2.csv"),
		CopySource: aws.String(UsersBucketForUnitTest + "/UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2.csv"),
	},
}
mockS3.QueuedCopyObjectOutput = []*s3.CopyObjectOutput{
	{},
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{1234567890},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/bless/rtt-456/job2", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (QuantHandler_Comparison_FailRemainingPointsCheck)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"], "remainingPointsPMCs": [4,6,88]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/RemainingPoints", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"]}`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Unexpected PMCs supplied for ROI: roi-567

400
No PMCs supplied for RemainingPoints ROI
Example (QuantHandler_Comparison_FailReqBody)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{quantIDs":["quant-345", "quant-789"]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":[]}`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Request body invalid

400
Requested with 0 quant IDs
Example (QuantHandler_Comparison_Fail_Dataset)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

getRequests, getResponses := prepROICompareGetCalls()

// Blank out the dataset
getResponses[1] = nil

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

dataset.bin <nil>
combined.bin <nil>
404
Failed to download dataset: NoSuchKey: Returning error from GetObject
Example (QuantHandler_Comparison_Fail_ROI)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

getRequests, getResponses := prepROICompareGetCalls()

// Blank out the ROI
getResponses[0] = nil

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

dataset.bin <nil>
combined.bin <nil>
404
ROI ID roi-567 not found
Example (QuantHandler_Comparison_OK)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

getRequests, getResponses := prepROICompareGetCalls()

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// This'll read PMC 90 and 95 and do the averaging of those... Deliberately different to the calculateTotals tests!
Output:

dataset.bin <nil>
combined.bin <nil>
200
{
    "roiID": "roi-567",
    "quantTables": [
        {
            "quantID": "quant-345",
            "quantName": "quant test v6 Al \u003e 100%",
            "elementWeights": {
                "CaO_%": 7.6938,
                "FeO-T_%": 12.49375,
                "SiO2_%": 40.1224,
                "TiO2_%": 0.28710002
            }
        },
        {
            "quantID": "quant-789",
            "quantName": "quant test v6 Al \u003e 100%",
            "elementWeights": {
                "CaO_%": 7.6938,
                "FeO-T_%": 12.49375,
                "SiO2_%": 40.1224,
                "TiO2_%": 0.28710002
            }
        }
    ]
}
Example (QuantHandler_DeleteSharedJob)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.AllowDeleteInAnyOrder = true

// Listing log files for each deletion
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/rtt-456/Quantifications/job3-logs/"),
	},
}

// First has logs, second has none (old style quant), third has 1 file
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/shared/rtt-456/Quantifications/job3-logs/log001.txt")},
		},
	},
}

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job3.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"jobId": "job3",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job3.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/job3.bin"),
	},
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobStatus/rtt-456/job3-status.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/job3.csv"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/job3-logs/log001.txt"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
	{},
	{},
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Existant shared job, OK
req, _ := http.NewRequest("DELETE", "/quantification/rtt-456/shared-job3", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (QuantHandler_DeleteSharedJobNotExists)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job1.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Non-existant shared job, ERROR
req, _ := http.NewRequest("DELETE", "/quantification/rtt-456/shared-job1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
job1 not found
Example (QuantHandler_DeleteUserJob)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.AllowDeleteInAnyOrder = true

// Listing log files for each deletion
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/"),
	},
}

// First has logs, second has none (old style quant), third has 1 file
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/log001.txt")},
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/log002.txt")},
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/log001_another.txt")},
		},
	},
}

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job2.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2.bin"),
	},
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobStatus/rtt-456/job2-status.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2.csv"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/log001.txt"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/log002.txt"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2-logs/log001_another.txt"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
	{},
	{},
	{},
	{},
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Existant job, OK
req, _ := http.NewRequest("DELETE", "/quantification/rtt-456/job2", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (QuantHandler_DeleteUserJobNotExist)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job1.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Non-existant job, ERROR
req, _ := http.NewRequest("DELETE", "/quantification/rtt-456/job1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
job1 not found
Example (QuantHandler_Fail_QuantFile)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

getRequests, getResponses := prepROICompareGetCalls()

// Blank out the first quant file
getResponses[2] = nil

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

dataset.bin <nil>
combined.bin <nil>
404
Failed to download quant quant-345: NoSuchKey: Returning error from GetObject
Example (QuantHandler_Fail_QuantSummary)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

getRequests, getResponses := prepROICompareGetCalls()

// Blank out the first quant summary file
getResponses[3] = nil

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/comparison-for-roi/dataset-123/roi-567", bytes.NewReader([]byte(`{"quantIDs":["quant-345", "quant-789"]}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

dataset.bin <nil>
combined.bin <nil>
404
Failed to download quant summary quant-345: NoSuchKey: Returning error from GetObject
Example (QuantHandler_Get)
const summaryJSON = `{
   "dataset_id": "rtt-456",
   "group": "the-group",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "0",
   "rtt": 456,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
}`
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job1.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job1.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job7.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/rtt-456/summary.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job7.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte("bad json"))),
	},
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174",
            "email": ""
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": "",
		"quantMode": "AB"
	},
	"jobId": "job1",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174",
            "email": ""
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job7",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
}

//signer := awsutil.MockSigner{[]string{"http://url1.com", "http://url2.com"}}
//svcs := MakeMockSvcs(&mockS3, nil, &signer)
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:wrong-group": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

// Dataset summary file wrong group, ERROR - NO ACCESS
req, _ := http.NewRequest("GET", "/quantification/rtt-456/job1", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

mockUser = pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:the-group": true,
	},
}

// Dataset summary has different group, ACCESS DENIED
req, _ = http.NewRequest("GET", "/quantification/rtt-456/job1", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Failed to parse summary JSON, ERROR
req, _ = http.NewRequest("GET", "/quantification/rtt-456/job1", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File not found, ERROR
req, _ = http.NewRequest("GET", "/quantification/rtt-456/job1", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File found, OK
req, _ = http.NewRequest("GET", "/quantification/rtt-456/job1", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Shared file not found, ERROR
req, _ = http.NewRequest("GET", "/quantification/rtt-456/shared-job7", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Shared file found, OK
req, _ = http.NewRequest("GET", "/quantification/rtt-456/shared-job7", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

403
dataset rtt-456 not permitted

500
failed to verify dataset group permission

404
rtt-456 not found

404
job1 not found

200
{
    "summary": {
        "shared": false,
        "params": {
            "pmcsCount": 93,
            "name": "my test quant",
            "dataBucket": "dev-pixlise-data",
            "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
            "datasetID": "rtt-456",
            "jobBucket": "dev-pixlise-piquant-jobs",
            "detectorConfig": "PIXL",
            "elements": [
                "Sc",
                "Cr"
            ],
            "parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
            "runTimeSec": 120,
            "coresPerNode": 6,
            "startUnixTime": 1589948988,
            "creator": {
                "name": "peternemere",
                "user_id": "600f2a0806b6c70071d3d174",
                "email": ""
            },
            "roiID": "ZcH49SYZ",
            "elementSetID": "",
            "piquantVersion": "",
            "quantMode": "AB",
            "comments": "",
            "roiIDs": []
        },
        "elements": [],
        "jobId": "job1",
        "status": "complete",
        "message": "Nodes ran: 1",
        "endUnixTime": 1589949035,
        "outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
        "piquantLogList": [
            "https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
            "https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
        ]
    },
    "url": "https:///quantification/download/rtt-456/job1"
}

404
job7 not found

200
{
    "summary": {
        "shared": false,
        "params": {
            "pmcsCount": 93,
            "name": "my test quant",
            "dataBucket": "dev-pixlise-data",
            "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
            "datasetID": "rtt-456",
            "jobBucket": "dev-pixlise-piquant-jobs",
            "detectorConfig": "PIXL",
            "elements": [
                "Sc",
                "Cr"
            ],
            "parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
            "runTimeSec": 120,
            "coresPerNode": 6,
            "startUnixTime": 1589948988,
            "creator": {
                "name": "peternemere",
                "user_id": "600f2a0806b6c70071d3d174",
                "email": ""
            },
            "roiID": "ZcH49SYZ",
            "elementSetID": "",
            "piquantVersion": "",
            "quantMode": "",
            "comments": "",
            "roiIDs": []
        },
        "elements": [
            "Sc",
            "Cr"
        ],
        "jobId": "job7",
        "status": "complete",
        "message": "Nodes ran: 1",
        "endUnixTime": 1589949035,
        "outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
        "piquantLogList": [
            "https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
            "https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
        ]
    },
    "url": "https:///quantification/download/rtt-456/shared-job7"
}
Example (QuantHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.AllowGetInAnyOrder = true

// Listing lists files in user dir, share dir, then gets datasets jobs.json and forms a single list of job summaries to send back
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/rtt-456/Quantifications/summary-"),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job1.json")},
			//{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-something.txt")},
		},
	},
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json")},
		},
	},
}

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job1.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobSummaries/rtt-456-jobs.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/rtt-456/Quantifications/blessed-quant.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174",
            "email": ""
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"jobId": "job1",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"node00001_stdout.log",
		"node00001_threads.log"
	]
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": true,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174",
            "email": ""
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"node00001_stdout.log",
		"node00001_threads.log"
	]
}`))),
	},
	// Only job 5 should be sent out, job 3 is completed, job 4 is another dataset
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"job3": {
		"params": {
			"pmcsCount": 93,
			"name": "in progress quant",
			"dataBucket": "dev-pixlise-data",
			"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
			"datasetID": "rtt-456",
			"jobBucket": "dev-pixlise-piquant-jobs",
			"detectorConfig": "PIXL",
			"elements": [
				"Sc",
				"Cr"
			],
			"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
			"runTimeSec": 120,
			"coresPerNode": 6,
			"startUnixTime": 1589948988,
			"creator": {
				"name": "peternemere",
				"user_id": "600f2a0806b6c70071d3d174",
                "email": ""
			},
			"roiID": "ZcH49SYZ",
			"elementSetID": ""
		},
		"jobId": "job3",
		"status": "complete",
		"message": "Something about the nodes"
	},
	"job5": {
		"params": {
			"pmcsCount": 93,
			"name": "in progress quant",
			"dataBucket": "dev-pixlise-data",
			"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
			"datasetID": "rtt-456",
			"jobBucket": "dev-pixlise-piquant-jobs",
			"detectorConfig": "PIXL",
			"elements": [
				"Sc",
				"Cr"
			],
			"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
			"runTimeSec": 120,
			"coresPerNode": 6,
			"startUnixTime": 1589948988,
			"creator": {
				"name": "peternemere",
				"user_id": "600f2a0806b6c70071d3d174",
                "email": ""
			},
			"roiID": "ZcH49SYZ",
			"elementSetID": ""
		},
		"elements": ["Cr03", "Sc"],
		"jobId": "job5",
		"status": "nodes_running",
		"message": "Something about the nodes"
	},
	"job1": {
		"params": {
			"pmcsCount": 93,
			"name": "in progress quant",
			"dataBucket": "dev-pixlise-data",
			"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
			"datasetID": "rtt-456",
			"jobBucket": "dev-pixlise-piquant-jobs",
			"detectorConfig": "PIXL",
			"elements": [
				"Sc",
				"Cr"
			],
			"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
			"runTimeSec": 120,
			"coresPerNode": 6,
			"startUnixTime": 1589948988,
			"creator": {
				"name": "peternemere",
				"user_id": "600f2a0806b6c70071d3d174",
                "email": ""
			},
			"roiID": "ZcH49SYZ",
			"elementSetID": ""
		},
		"jobId": "job1",
		"status": "nodes_running",
		"message": "Not done yet"
	}
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"history": [
		{
			"version": 1,
			"blessedAt": 1234567090,
			"userId": "600f2a0806b6c70071d3d174",
			"userName": "Niko Bellic",
			"jobId": "jobAAA"
		},
		{
			"version": 3,
			"blessedAt": 1234567690,
			"userId": "555555",
			"userName": "Michael Da Santa",
			"jobId": "job2"
		},
		{
			"version": 2,
			"blessedAt": 1234567490,
			"userId": "600f2a0806b6c70071d3d174",
			"userName": "Niko Bellic",
			"jobId": "jobBBB"
		}
	]
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/quantification/rtt-456", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "summaries": [
        {
            "shared": false,
            "params": {
                "pmcsCount": 93,
                "name": "my test quant",
                "dataBucket": "dev-pixlise-data",
                "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
                "datasetID": "rtt-456",
                "jobBucket": "dev-pixlise-piquant-jobs",
                "detectorConfig": "PIXL",
                "elements": [
                    "Sc",
                    "Cr"
                ],
                "parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
                "runTimeSec": 120,
                "coresPerNode": 6,
                "startUnixTime": 1589948988,
                "creator": {
                    "name": "peternemere",
                    "user_id": "600f2a0806b6c70071d3d174",
                    "email": ""
                },
                "roiID": "ZcH49SYZ",
                "elementSetID": "",
                "piquantVersion": "",
                "quantMode": "",
                "comments": "",
                "roiIDs": []
            },
            "elements": [],
            "jobId": "job1",
            "status": "complete",
            "message": "Nodes ran: 1",
            "endUnixTime": 1589949035,
            "outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
            "piquantLogList": [
                "node00001_stdout.log",
                "node00001_threads.log"
            ]
        },
        {
            "shared": false,
            "params": {
                "pmcsCount": 93,
                "name": "in progress quant",
                "dataBucket": "dev-pixlise-data",
                "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
                "datasetID": "rtt-456",
                "jobBucket": "dev-pixlise-piquant-jobs",
                "detectorConfig": "PIXL",
                "elements": [
                    "Sc",
                    "Cr"
                ],
                "parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
                "runTimeSec": 120,
                "coresPerNode": 6,
                "startUnixTime": 1589948988,
                "creator": {
                    "name": "peternemere",
                    "user_id": "600f2a0806b6c70071d3d174",
                    "email": ""
                },
                "roiID": "ZcH49SYZ",
                "elementSetID": "",
                "piquantVersion": "",
                "quantMode": "",
                "comments": "",
                "roiIDs": []
            },
            "elements": [
                "Cr03",
                "Sc"
            ],
            "jobId": "job5",
            "status": "nodes_running",
            "message": "Something about the nodes",
            "endUnixTime": 0,
            "outputFilePath": "",
            "piquantLogList": null
        },
        {
            "shared": true,
            "params": {
                "pmcsCount": 93,
                "name": "my test quant",
                "dataBucket": "dev-pixlise-data",
                "datasetPath": "Datasets/rtt-456/5x5dataset.bin",
                "datasetID": "rtt-456",
                "jobBucket": "dev-pixlise-piquant-jobs",
                "detectorConfig": "PIXL",
                "elements": [
                    "Sc",
                    "Cr"
                ],
                "parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
                "runTimeSec": 120,
                "coresPerNode": 6,
                "startUnixTime": 1589948988,
                "creator": {
                    "name": "peternemere",
                    "user_id": "600f2a0806b6c70071d3d174",
                    "email": ""
                },
                "roiID": "ZcH49SYZ",
                "elementSetID": "",
                "piquantVersion": "",
                "quantMode": "",
                "comments": "",
                "roiIDs": []
            },
            "elements": [],
            "jobId": "shared-job2",
            "status": "complete",
            "message": "Nodes ran: 1",
            "endUnixTime": 1589949035,
            "outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
            "piquantLogList": [
                "node00001_stdout.log",
                "node00001_threads.log"
            ]
        }
    ],
    "blessedQuant": {
        "version": 3,
        "blessedAt": 1234567690,
        "userId": "555555",
        "userName": "Michael Da Santa",
        "jobId": "shared-job2"
    }
}
Example (QuantHandler_MultiQuantCombine_CombineIncompatible)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
	/*		{
			Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/dataset-123/Quantifications/summary-"),
		},*/*/
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
	/*		{
			Contents: []*s3.Object{
				{Key: aws.String("UserContent/shared/dataset-123/Quantifications/summary-job2.json")},
			},
		},*/},*/
}

getRequests, getResponses := prepCombineGetCalls("AB.bin")

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
400
Detectors don't match other quantifications: quant-456
Example (QuantHandler_MultiQuantCombine_DatasetFailsToLoad)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
	/*		{
			Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/dataset-123/Quantifications/summary-"),
		},*/*/
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
	/*		{
			Contents: []*s3.Object{
				{Key: aws.String("UserContent/shared/dataset-123/Quantifications/summary-job2.json")},
			},
		},*/},*/
}

getRequests, getResponses := prepCombineGetCalls("combined-3elem.bin")
getResponses[2] = nil // Dataset bin file

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
404
Failed to download dataset: NoSuchKey: Returning error from GetObject
Example (QuantHandler_MultiQuantCombine_DuplicateNameWithInProgressQuant)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
}

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// First retrieves the quant summaries that came from uniqueness check, to read the quant name
	{
		Bucket: aws.String(jobBucketForUnitTest), Key: aws.String("JobSummaries/dataset-123-jobs.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(multiCombineJobSummary))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "in progress",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Name already used: in progress
Example (QuantHandler_MultiQuantCombine_OK)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
}

getRequests, getResponses := prepCombineGetCalls("combined-3elem.bin")
mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

expBinBytes := []byte{10, 8, 108, 105, 118, 101, 116, 105, 109, 101, 10, 5, 67, 97, 79, 95, 37, 10, 7, 67, 97, 79, 95, 101, 114, 114, 10, 7, 70, 101, 79, 45, 84, 95, 37, 10, 9, 70, 101, 79, 45, 84, 95, 101, 114, 114, 10, 6, 83, 105, 79, 50, 95, 37, 10, 8, 83, 105, 79, 50, 95, 101, 114, 114, 10, 6, 84, 105, 79, 50, 95, 37, 10, 8, 84, 105, 79, 50, 95, 101, 114, 114, 18, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 26, 192, 2, 10, 8, 67, 111, 109, 98, 105, 110, 101, 100, 18, 60, 8, 97, 42, 0, 42, 5, 21, 129, 38, 236, 64, 42, 5, 21, 205, 204, 204, 62, 42, 5, 21, 163, 82, 28, 66, 42, 5, 21, 0, 0, 0, 64, 42, 5, 21, 0, 0, 128, 191, 42, 5, 21, 0, 0, 128, 191, 42, 5, 21, 90, 100, 219, 62, 42, 5, 21, 205, 204, 76, 62, 18, 60, 8, 98, 42, 0, 42, 5, 21, 251, 92, 149, 64, 42, 5, 21, 154, 153, 153, 62, 42, 5, 21, 64, 19, 81, 65, 42, 5, 21, 51, 51, 51, 63, 42, 5, 21, 120, 122, 237, 65, 42, 5, 21, 0, 0, 192, 63, 42, 5, 21, 25, 4, 6, 63, 42, 5, 21, 205, 204, 76, 62, 18, 60, 8, 100, 42, 0, 42, 5, 21, 204, 238, 57, 64, 42, 5, 21, 0, 0, 0, 63, 42, 5, 21, 26, 81, 99, 65, 42, 5, 21, 51, 51, 51, 63, 42, 5, 21, 0, 0, 128, 191, 42, 5, 21, 0, 0, 128, 191, 42, 5, 21, 182, 243, 13, 63, 42, 5, 21, 205, 204, 76, 62, 18, 60, 8, 101, 42, 0, 42, 5, 21, 27, 158, 118, 64, 42, 5, 21, 0, 0, 0, 63, 42, 5, 21, 37, 117, 192, 65, 42, 5, 21, 154, 153, 153, 63, 42, 5, 21, 205, 59, 22, 66, 42, 5, 21, 51, 51, 243, 63, 42, 5, 21, 141, 151, 174, 62, 42, 5, 21, 205, 204, 76, 62, 18, 60, 8, 104, 42, 0, 42, 5, 21, 50, 119, 183, 64, 42, 5, 21, 154, 153, 153, 62, 42, 5, 21, 239, 201, 5, 65, 42, 5, 21, 205, 204, 204, 62, 42, 5, 21, 42, 105, 2, 66, 42, 5, 21, 205, 204, 204, 63, 42, 5, 21, 86, 125, 174, 62, 42, 5, 21, 205, 204, 76, 62}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/multi_combquant123.bin"), Body: bytes.NewReader(expBinBytes),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/multi_combquant123.csv"), Body: bytes.NewReader([]byte(`Combined multi-quantification from quant-123, quant-456
PMC, RTT, SCLK, filename, livetime, CaO_%, CaO_err, FeO-T_%, FeO-T_err, SiO2_%, SiO2_err, TiO2_%, TiO2_err
97, 0, 0, Normal_Combined_roi-second, 0, 7.379700183868408, 0.4000000059604645, 39.0806999206543, 2, -1, -1, 0.4284999966621399, 0.20000000298023224
98, 0, 0, Normal_Combined_roi-first, 0, 4.667600154876709, 0.30000001192092896, 13.06719970703125, 0.699999988079071, 29.684799194335938, 1.5, 0.5235000252723694, 0.20000000298023224
100, 0, 0, Normal_Combined_roi-second, 0, 2.9052000045776367, 0.5, 14.207300186157227, 0.699999988079071, -1, -1, 0.5544999837875366, 0.20000000298023224
101, 0, 0, Normal_Combined_shared-roi-third, 0, 3.8533999919891357, 0.5, 24.057199478149414, 1.2000000476837158, 37.55839920043945, 1.899999976158142, 0.3409999907016754, 0.20000000298023224
104, 0, 0, Normal_Combined_roi-first, 0, 5.73330020904541, 0.30000001192092896, 8.361800193786621, 0.4000000059604645, 32.602699279785156, 1.600000023841858, 0.3407999873161316, 0.20000000298023224
`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-multi_combquant123.json"), Body: bytes.NewReader([]byte(`{
    "shared": false,
    "params": {
        "pmcsCount": 0,
        "name": "new multi",
        "dataBucket": "datasets-bucket",
        "datasetPath": "Datasets/dataset-123/dataset.bin",
        "datasetID": "dataset-123",
        "jobBucket": "job-bucket",
        "detectorConfig": "",
        "elements": [
            "CaO",
            "FeO-T",
            "SiO2",
            "TiO2"
        ],
        "parameters": "",
        "runTimeSec": 0,
        "coresPerNode": 0,
        "startUnixTime": 4234567890,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        },
        "roiID": "",
        "elementSetID": "",
        "piquantVersion": "N/A",
        "quantMode": "CombinedMultiQuant",
        "comments": "combined quants",
        "roiIDs": [],
        "command": "map"
    },
    "elements": [
        "CaO",
        "FeO-T",
        "SiO2",
        "TiO2"
    ],
    "jobId": "multi_combquant123",
    "status": "complete",
    "message": "combined-multi quantification processed",
    "endUnixTime": 4234567890,
    "outputFilePath": "UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications",
    "piquantLogList": []
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
}

mockS3.AllowGetInAnyOrder = true

var idGen MockIDGenerator
idGen.ids = []string{"combquant123"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{4234567890},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
printMultiLineBody(resp.Body.String())
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
200
"multi_combquant123"
Example (QuantHandler_MultiQuantCombine_QuantFailsToLoad)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
	/*		{
			Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/dataset-123/Quantifications/summary-"),
		},*/*/
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
	/*		{
			Contents: []*s3.Object{
				{Key: aws.String("UserContent/shared/dataset-123/Quantifications/summary-job2.json")},
			},
		},*/},*/
}

getRequests, getResponses := prepCombineGetCalls("combined-3elem.bin")
getResponses[5] = nil // First quant load fail

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
printMultiLineBody(resp.Body.String())
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
404
Failed to download quant quant-123: NoSuchKey: Returning error from GetObject
Example (QuantHandler_MultiQuantCombine_ROINotFound)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
	/*		{
			Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/dataset-123/Quantifications/summary-"),
		},*/*/
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
	/*		{
			Contents: []*s3.Object{
				{Key: aws.String("UserContent/shared/dataset-123/Quantifications/summary-job2.json")},
			},
		},*/},*/
}

getRequests, getResponses := prepCombineGetCalls("combined-3elem.bin")
mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "non-existant-roi",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
printMultiLineBody(resp.Body.String())
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
404
Failed to get all ROIs: Failed to find ROI ID: non-existant-roi
Example (QuantHandler_MultiQuantCombine_SimpleFails)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "here's a name",
		"description": "combined quants",
		"roiZStack": []
	}`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "here's a name",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Name cannot be empty

400
Must reference more than 1 ROI

400
Must reference more than 1 ROI
Example (QuantHandler_MultiQuantCombine_SummaryOnly_OK)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
}

getRequests, getResponses := prepCombineGetCalls("combined-3elem.bin")
mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true

var idGen MockIDGenerator
idGen.ids = []string{"combquant123"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{4234567890},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		],
		"summaryOnly": true
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
printMultiLineBody(resp.Body.String())
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
200
{
    "detectors": [
        "Combined"
    ],
    "weightPercents": {
        "CaO": {
            "values": [
                0.10906311
            ],
            "roiIDs": [
                "roi-first",
                "roi-second",
                "shared-roi-third"
            ],
            "roiNames": [
                "1st ROI",
                "Second ROI",
                "Third ROI (shared)"
            ]
        },
        "FeO-T": {
            "values": [
                0.43899643
            ],
            "roiIDs": [
                "roi-first",
                "roi-second",
                "shared-roi-third"
            ],
            "roiNames": [
                "1st ROI",
                "Second ROI",
                "Third ROI (shared)"
            ]
        },
        "SiO2": {
            "values": [
                0.44375953
            ],
            "roiIDs": [
                "roi-first",
                "shared-roi-third"
            ],
            "roiNames": [
                "1st ROI",
                "Third ROI (shared)"
            ]
        },
        "TiO2": {
            "values": [
                0.009725777
            ],
            "roiIDs": [
                "roi-first",
                "roi-second",
                "shared-roi-third"
            ],
            "roiNames": [
                "1st ROI",
                "Second ROI",
                "Third ROI (shared)"
            ]
        }
    }
}
Example (QuantHandler_MultiQuantCombine_UserROIFailsToLoad)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Listing quants for name uniqueness check
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-"),
	},
	/*		{
			Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String("UserContent/shared/dataset-123/Quantifications/summary-"),
		},*/*/
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String("UserContent/600f2a0806b6c70071d3d174/dataset-123/Quantifications/summary-job1.json")},
		},
	},
	/*		{
			Contents: []*s3.Object{
				{Key: aws.String("UserContent/shared/dataset-123/Quantifications/summary-job2.json")},
			},
		},*/},*/
}

getRequests, getResponses := prepCombineGetCalls("combined-3elem.bin")
getResponses[3] = nil // User ROI load fail

mockS3.ExpGetObjectInput = getRequests
mockS3.QueuedGetObjectOutput = getResponses

mockS3.AllowGetInAnyOrder = true

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/combine/dataset-123", bytes.NewReader([]byte(`{
		"name": "new multi",
		"description": "combined quants",
		"roiZStack": [
			{
				"roiID": "roi-first",
				"quantificationID": "quant-123"
			},
			{
				"roiID": "roi-second",
				"quantificationID": "quant-456"
			},
			{
				"roiID": "shared-roi-third",
				"quantificationID": "quant-123"
			}
		]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
printMultiLineBody(resp.Body.String())
Output:

dataset <nil>
quant1 <nil>
quant2 <nil>
404
Failed to get all ROIs: Failed to find ROI ID: roi-first
Example (QuantHandler_ShareQuant)
rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Getting the quant summary to verify it exists
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job2.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174"
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": ""
	},
	"elements": ["Sc", "Cr"],
	"jobId": "job2",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
}

mockS3.ExpCopyObjectInput = []s3.CopyObjectInput{
	{
		Bucket:     aws.String(UsersBucketForUnitTest),
		Key:        aws.String("UserContent/shared/rtt-456/Quantifications/job2.bin"),
		CopySource: aws.String(UsersBucketForUnitTest + "/UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2.bin"),
	},
	{
		Bucket:     aws.String(UsersBucketForUnitTest),
		Key:        aws.String("UserContent/shared/rtt-456/Quantifications/summary-job2.json"),
		CopySource: aws.String(UsersBucketForUnitTest + "/UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-job2.json"),
	},
	{
		Bucket:     aws.String(UsersBucketForUnitTest),
		Key:        aws.String("UserContent/shared/rtt-456/Quantifications/job2.csv"),
		CopySource: aws.String(UsersBucketForUnitTest + "/UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/job2.csv"),
	},
}
mockS3.QueuedCopyObjectOutput = []*s3.CopyObjectOutput{
	{},
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/share/quantification/rtt-456/job2", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
"shared"
Example (QuantHandler_Stream_404)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/quantification/download/590340/job-7", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
590340 not found
Example (QuantHandler_Stream_BadSummary)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte("bad json"))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/quantification/download/590340/job-7", nil) // Should return empty list, datasets.json fails to download
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

500
failed to verify dataset group permission
Example (QuantHandler_UploadFails)

Quantification manual uploads, this has many failure scenarios...

rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{1234567890},
}
apiRouter := MakeRouter(svcs)

// No body
req, _ := http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// No name line
req, _ = http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Comments=Hello world
CSV
Header line
PMC,Ca_%,livetime,RTT,SCLK,filename
1,5.3,9.9,98765,1234567890,Normal_A
`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// No comment line
req, _ = http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Name=Hello world
CSV
Header line
PMC,Ca_%,livetime,RTT,SCLK,filename
1,5.3,9.9,98765,1234567890,Normal_A
`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// No/bad CSV line
req, _ = http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Name=Hello world
Comments=The test
Header line
PMC,Ca_%,livetime,RTT,SCLK,filename
1,5.3,9.9,98765,1234567890,Normal_A
`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Missing header line
req, _ = http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Name=Hello world
Comments=The test
CSV
PMC,Ca_%,livetime,RTT,SCLK,filename
1,5.3,9.9,98765,1234567890,Normal_A
`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Missing PMC column
req, _ = http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Name=Hello world
Comments=The test
CSV
Header line
Ca_%,livetime,RTT,SCLK,filename
5.3,9.9,98765,1234567890,Normal_A
`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
/*
   	// PMC not the first column
   	req, _ = http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Name=Hello world
   Comments=The test
   CSV
   Header line
   Ca_%,PMC,livetime,RTT,SCLK,filename
   5.3,1,9.9,98765,1234567890,Normal_A
   `)))
   	resp = executeRequest(req, apiRouter.Router)

   	fmt.Println(resp.Code)
   	fmt.Println(resp.Body)
*/sp.Body)
*/
Output:

Example (QuantHandler_UploadOK)

Quantification manual uploads, success scenario

rand.Seed(time.Now().UnixNano())
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// bin, CSV and summary are all uploaded
expBinBytes := []byte{10, 4, 67, 97, 95, 37, 10, 8, 108, 105, 118, 101, 116, 105, 109, 101, 18, 2, 0, 0, 26, 31, 10, 1, 65, 18, 26, 8, 1, 16, 205, 131, 6, 24, 210, 133, 216, 204, 4, 42, 5, 21, 154, 153, 169, 64, 42, 5, 21, 102, 102, 30, 65, 26, 31, 10, 1, 66, 18, 26, 8, 1, 16, 205, 131, 6, 24, 210, 133, 216, 204, 4, 42, 5, 21, 102, 102, 166, 64, 42, 5, 21, 205, 204, 28, 65}

//fmt.Println(string(expBinBytes))
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/upload_quant123.bin"), Body: bytes.NewReader(expBinBytes),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/upload_quant123.csv"), Body: bytes.NewReader([]byte(`Header line
PMC, Ca_%, livetime, RTT, SCLK, filename
1, 5.3, 9.9, 98765, 1234567890, Normal_A
1, 5.2, 9.8, 98765, 1234567890, Normal_B
`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications/summary-upload_quant123.json"), Body: bytes.NewReader([]byte(`{
    "shared": false,
    "params": {
        "pmcsCount": 0,
        "name": "Hello world",
        "dataBucket": "datasets-bucket",
        "datasetPath": "Datasets/rtt-456/dataset.bin",
        "datasetID": "rtt-456",
        "jobBucket": "job-bucket",
        "detectorConfig": "",
        "elements": [
            "Ca"
        ],
        "parameters": "",
        "runTimeSec": 0,
        "coresPerNode": 0,
        "startUnixTime": 1234567890,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        },
        "roiID": "",
        "elementSetID": "",
        "piquantVersion": "N/A",
        "quantMode": "ABManual",
        "comments": "The test",
        "roiIDs": [],
        "command": "map"
    },
    "elements": [
        "Ca"
    ],
    "jobId": "upload_quant123",
    "status": "complete",
    "message": "user-supplied quantification processed",
    "endUnixTime": 1234567890,
    "outputFilePath": "UserContent/600f2a0806b6c70071d3d174/rtt-456/Quantifications",
    "piquantLogList": []
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"quant123"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
svcs.TimeStamper = &services.MockTimeNowStamper{
	QueuedTimeStamps: []int64{1234567890},
}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/quantification/upload/rtt-456", bytes.NewReader([]byte(`Name=Hello world
Comments=The test
CSV
Header line
PMC, Ca_%, livetime, RTT, SCLK, filename
1, 5.3, 9.9, 98765, 1234567890, Normal_A
1, 5.2, 9.8, 98765, 1234567890, Normal_B
`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
"upload_quant123"
Example (QuantHandler_ZStackLoad)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(zstackS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(zstackS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(zstackS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(zstackS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte("xSomething invalid"))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(zStackFile))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// File not in S3, should return empty
req, _ := http.NewRequest("GET", "/quantification/combine-list/dataset123", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File in S3 empty, should return empty
req, _ = http.NewRequest("GET", "/quantification/combine-list/dataset123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains invalid json, should return error
req, _ = http.NewRequest("GET", "/quantification/combine-list/dataset123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File valid, should be OK
req, _ = http.NewRequest("GET", "/quantification/combine-list/dataset123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "roiZStack": []
}

200
{
    "roiZStack": []
}

500
invalid character 'x' looking for beginning of value

200
{
    "roiZStack": [
        {
            "roiID": "roi123",
            "quantificationID": "quantOne"
        },
        {
            "roiID": "roi456",
            "quantificationID": "quantTwo"
        }
    ]
}
Example (QuantHandler_ZStackSave)

Super simple because we overwrite the file each time!

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/datasetThatDoesntExist/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/datasetNoPermission/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/dataset123/summary.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"dataset_id": "datasetNoPermission",
				"group": "GroupA",
				"drive_id": 292,
				"site_id": 1,
				"target_id": "?",
				"sol": "0",
				"rtt": 456,
				"sclk": 0,
				"context_image": "MCC-234.png",
				"location_count": 446,
				"data_file_size": 2699388,
				"context_images": 1,
				"normal_spectra": 882,
				"dwell_spectra": 0,
				"bulk_spectra": 2,
				"max_spectra": 2,
				"pseudo_intensities": 441,
				"detector_config": "PIXL"
			 }`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"dataset_id": "dataset123",
				"group": "GroupB",
				"drive_id": 292,
				"site_id": 1,
				"target_id": "?",
				"sol": "0",
				"rtt": 456,
				"sclk": 0,
				"context_image": "MCC-234.png",
				"location_count": 446,
				"data_file_size": 2699388,
				"context_images": 1,
				"normal_spectra": 882,
				"dwell_spectra": 0,
				"bulk_spectra": 2,
				"max_spectra": 2,
				"pseudo_intensities": 441,
				"detector_config": "PIXL"
			 }`))),
	},
}

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(zstackS3Path), Body: bytes.NewReader([]byte(zStackFile)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)

mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:GroupB": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}

apiRouter := MakeRouter(svcs)

// Invalid contents, fail
req, _ := http.NewRequest("POST", "/quantification/combine-list/dataset123", bytes.NewReader([]byte("Something invalid")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Bad dataset ID, fail
req, _ = http.NewRequest("POST", "/quantification/combine-list/datasetThatDoesntExist", bytes.NewReader([]byte(zStackFile)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// No permissions for dataset, fail
req, _ = http.NewRequest("POST", "/quantification/combine-list/datasetNoPermission", bytes.NewReader([]byte(zStackFile)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Valid, success
req, _ = http.NewRequest("POST", "/quantification/combine-list/dataset123", bytes.NewReader([]byte(zStackFile)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Request body invalid

400
datasetThatDoesntExist not found

400
dataset datasetNoPermission not permitted

200
Example (Quant_LastRun_Stream_OK)
const summaryJSON = `{
   "dataset_id": "590340",
   "group": "groupie",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "0",
   "rtt": 590340,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
}`

quantResultBytes := []byte{60, 113, 117, 97, 110, 116, 62}

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/590340/LastPiquantOutput/quant/output_data.csv"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(quantResultBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(quantResultBytes)), // return some printable chars so easier to compare in Output comment
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

// Should fail, datasets.json fails to download
req, _ := http.NewRequest("GET", "/quantification/last/download/590340/quant/output", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
// Make sure the response is the right kind...
fmt.Println(resp.HeaderMap["Content-Disposition"])
fmt.Println(resp.HeaderMap["Cache-Control"])
fmt.Println(resp.HeaderMap["Content-Length"])
fmt.Println(resp.Body)

// Should fail, invalid command
req, _ = http.NewRequest("GET", "/quantification/last/download/590340/rpmcalc/output", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
// Make sure the response is the right kind...
fmt.Println(resp.HeaderMap["Content-Disposition"])
fmt.Println(resp.HeaderMap["Cache-Control"])
fmt.Println(resp.HeaderMap["Content-Length"])
fmt.Println(resp.Body)

// Should fail, invalid file type
req, _ = http.NewRequest("GET", "/quantification/last/download/590340/quant/runcost", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
// Make sure the response is the right kind...
fmt.Println(resp.HeaderMap["Content-Disposition"])
fmt.Println(resp.HeaderMap["Cache-Control"])
fmt.Println(resp.HeaderMap["Content-Length"])
fmt.Println(resp.Body)

// Should succeed
req, _ = http.NewRequest("GET", "/quantification/last/download/590340/quant/output", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
// Make sure the response is the right kind...
fmt.Println(resp.HeaderMap["Content-Disposition"])
fmt.Println(resp.HeaderMap["Cache-Control"])
fmt.Println(resp.HeaderMap["Content-Length"])
fmt.Println(resp.Body)
Output:

404
[]
[]
[]
590340 not found

400
[]
[]
[]
Invalid request

400
[]
[]
[]
Invalid request

200
[attachment; filename="output_data.csv"]
[max-age=604800]
[7]
<quant>
Example (Quant_Stream_OK)
const summaryJSON = `{
   "dataset_id": "590340",
   "group": "groupie",
   "drive_id": 292,
   "site_id": 1,
   "target_id": "?",
   "sol": "0",
   "rtt": 590340,
   "sclk": 0,
   "context_image": "MCC-234.png",
   "location_count": 446,
   "data_file_size": 2699388,
   "context_images": 1,
   "normal_spectra": 882,
   "dwell_spectra": 0,
   "bulk_spectra": 2,
   "max_spectra": 2,
   "pseudo_intensities": 441,
   "detector_config": "PIXL"
}`

datasetBytes := []byte{60, 113, 117, 97, 110, 116, 62}

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(DatasetsBucketForUnitTest), Key: aws.String("Datasets/590340/summary.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/590340/Quantifications/job-7.bin"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(summaryJSON))),
	},
	{
		ContentLength: aws.Int64(int64(len(datasetBytes))),
		Body:          ioutil.NopCloser(bytes.NewReader(datasetBytes)), // return some printable chars so easier to compare in Output comment
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
mockUser := pixlUser.UserInfo{
	Name:   "Niko Bellic",
	UserID: "600f2a0806b6c70071d3d174",
	Permissions: map[string]bool{
		"access:groupie": true,
	},
}
svcs.JWTReader = MockJWTReader{InfoToReturn: &mockUser}
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/quantification/download/590340/job-7", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
// Make sure the response is the right kind...
fmt.Println(resp.HeaderMap["Content-Disposition"])
fmt.Println(resp.HeaderMap["Cache-Control"])
fmt.Println(resp.HeaderMap["Content-Length"])
fmt.Println(resp.Body)
Output:

200
[attachment; filename="job-7.bin"]
[max-age=604800]
[7]
<quant>
Example (RegisterExportHandlerBadJSONBody)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
var exp MockExporter
exp.downloadReturn = []byte{80, 101, 116, 101, 114, 32, 105, 115, 32, 97, 119, 101, 115, 111, 109, 101}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Exporter = &exp
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/export/files/983561", bytes.NewReader([]byte(`{
		"quantificati,`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Print(string(resp.Body.Bytes()))
Output:

400
unexpected end of JSON input
Example (RegisterExportHandlerMissingColumn)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
var exp MockExporter
exp.downloadReturn = []byte{80, 101, 116, 101, 114, 32, 105, 115, 32, 97, 119, 101, 115, 111, 109, 101}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Exporter = &exp
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/export/files/983561", bytes.NewReader([]byte(`{
		"fileName": "test.zip",
		"quantificationId": "abc123"
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Print(string(resp.Body.Bytes()))
Output:

400
No File IDs specified, nothing to export
Example (RegisterExportHandlerMissingFileName)

Same result as if the POST body is completely empty

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
var exp MockExporter
exp.downloadReturn = []byte{80, 101, 116, 101, 114, 32, 105, 115, 32, 97, 119, 101, 115, 111, 109, 101}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Exporter = &exp
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/export/files/983561", bytes.NewReader([]byte(`{
		"fileIds": ["quant-maps-csv", "rois"]
	}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Print(string(resp.Body.Bytes()))
Output:

400
File name must end in .zip
Example (RegisterExportHandlerSunny)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
var exp MockExporter
exp.downloadReturn = []byte{80, 101, 116, 101, 114, 32, 105, 115, 32, 97, 119, 101, 115, 111, 109, 101}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
svcs.Exporter = &exp
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/export/files/983561", bytes.NewReader([]byte(`{
		"fileName": "test.zip",
		"quantificationId": "abc123",
		"fileIds": ["quant-maps-csv", "rois"]
	}`)))
resp := executeRequest(req, apiRouter.Router)
fmt.Println(resp.Code)

fmt.Println(exp.datasetID)
fmt.Println(exp.userID)
fmt.Println(exp.quantID)
fmt.Println(exp.fileIDs)

fmt.Println(resp.Header().Get("Content-Type"))
fmt.Println(resp.Header().Get("Content-Length"))
fmt.Println(string(resp.Body.Bytes()))
Output:

200
983561
600f2a0806b6c70071d3d174
abc123
[quant-maps-csv rois]
application/octet-stream
16
Peter is awesome
Example (RegisterMetricsHandlerTest)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("POST", "/metrics", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(string(resp.Body.Bytes()))
Output:

200
"{}"
Example (RoiHandler_Delete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "99": {
        "name": "Shared item to delete",
        "locationIndexes": [33],
        "description": "",
        "shared": false,
        "creator": {
            "name": "The user who can delete",
            "user_id": "600f2a0806b6c70071d3d174"
        }
    }
}`))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path), Body: bytes.NewReader([]byte(`{
    "772": {
        "name": "White spot",
        "locationIndexes": [
            14,
            5,
            94
        ],
        "description": "",
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path), Body: bytes.NewReader([]byte(`{}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Delete finds file missing, ERROR
req, _ := http.NewRequest("DELETE", "/roi/TheDataSetID/331", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds empty file, ERROR
req, _ = http.NewRequest("DELETE", "/roi/TheDataSetID/331", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete cant find item, ERROR
req, _ = http.NewRequest("DELETE", "/roi/TheDataSetID/22", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds item, OK
req, _ = http.NewRequest("DELETE", "/roi/TheDataSetID/331", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item but from wrong user, ERROR
req, _ = http.NewRequest("DELETE", "/roi/TheDataSetID/shared-331", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item, OK
req, _ = http.NewRequest("DELETE", "/roi/TheDataSetID/shared-99", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
331 not found

404
331 not found

404
22 not found

200

401
331 not owned by 600f2a0806b6c70071d3d174

200
Example (RoiHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/NewDataSet/ROI.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/NewDataSet/ROI.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/AnotherDataSetID/ROI.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/AnotherDataSetID/ROI.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil, // No file in S3
	nil, // No file in S3
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"331": {
					"name": "dark patch",
					"locationIndexes": [4, 55, 394],
					"shared": false,
					"creator": { "name": "Peter", "user_id": "u77", "email": "" },
					"imageName": "dtu_context_rgbu.tif"
				}
			}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"007": {
					"description": "james bonds shared ROI",
					"name": "james bond",
					"locationIndexes": [99],
					"shared": false,
					"creator": { "name": "Tom", "user_id": "u85", "email": ""}
				}
			}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/roi/NewDataSet", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/roi/TheDataSetID", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/roi/AnotherDataSetID", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{}

200
{}

200
{
    "331": {
        "name": "dark patch",
        "locationIndexes": [
            4,
            55,
            394
        ],
        "description": "",
        "imageName": "dtu_context_rgbu.tif",
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u77",
            "email": ""
        }
    },
    "shared-007": {
        "name": "james bond",
        "locationIndexes": [
            99
        ],
        "description": "james bonds shared ROI",
        "shared": true,
        "creator": {
            "name": "Tom",
            "user_id": "u85",
            "email": ""
        }
    }
}
Example (RoiHandler_Post)
func Example_roiHandler_Get() {
	var mockS3 awsutil.MockS3Client
	defer mockS3.FinishTest()
	mockS3.ExpGetObjectInput = []s3.GetObjectInput{
		{
			Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
		},
		{
			Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
		},
		{
			Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
		},
		{
			Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
		},
		{
			Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
		},
	}
	mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
		nil,
		{
			Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
		},
		{
			Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
		},
		{
			Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
		},
		{
			Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
		},
	}

	svcs := MakeMockSvcs(&mockS3, nil, nil)
	apiRouter := MakeRouter(svcs)

	// File not in S3, should return 404
	req, _ := http.NewRequest("GET", "/roi/TheDataSetID/331", nil)
	resp := executeRequest(req, apiRouter.Router)

	fmt.Println(resp.Code)
	fmt.Println(resp.Body)

	// File in S3 empty, should return 404
	req, _ = http.NewRequest("GET", "/roi/TheDataSetID/331", nil)
	resp = executeRequest(req, apiRouter.Router)

	fmt.Println(resp.Code)
	fmt.Println(resp.Body)

	// File contains stuff, using ID thats not in there, should return 404
	req, _ = http.NewRequest("GET", "/roi/TheDataSetID/222", nil)
	resp = executeRequest(req, apiRouter.Router)

	fmt.Println(resp.Code)
	fmt.Println(resp.Body)

	// File contains stuff, using ID that exists
	req, _ = http.NewRequest("GET", "/roi/TheDataSetID/331", nil)
	resp = executeRequest(req, apiRouter.Router)

	fmt.Println(resp.Code)
	fmt.Println(resp.Body)

	// Check that shared file was loaded if shared ID sent in
	req, _ = http.NewRequest("GET", "/roi/TheDataSetID/shared-331", nil)
	resp = executeRequest(req, apiRouter.Router)

	fmt.Println(resp.Code)
	fmt.Println(resp.Body)

	// Output:
	// 404
	// 331 not found
	//
	// 404
	// 331 not found
	//
	// 404
	// 222 not found
	//
	// 200
	// {
	//     "name": "Dark patch 2",
	//     "locationIndexes": [
	//         4,
	//         55,
	//         394
	//     ],
	//     "description": "The second dark patch",
	//     "shared": false,
	//     "creator": {
	//         "name": "Peter",
	//         "user_id": "u123",
    //         "email": ""
	//     }
	// }
	//
	// 200
	// {
	//     "name": "Dark patch 2",
	//     "locationIndexes": [
	//         4,
	//         55,
	//         394
	//     ],
	//     "description": "The second dark patch",
	//     "shared": true,
	//     "creator": {
	//         "name": "Peter",
	//         "user_id": "u123",
    //         "email": ""
	//     }
	// }
}
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "id999": {
        "name": "White spot",
        "locationIndexes": [
            3,
            9
        ],
        "description": "",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path), Body: bytes.NewReader([]byte(`{
    "id3": {
        "name": "White spot",
        "locationIndexes": [
            3,
            9,
            199
        ],
        "description": "Posted item!",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path), Body: bytes.NewReader([]byte(`{
    "id4": {
        "name": "White spot",
        "locationIndexes": [
            3,
            9,
            199
        ],
        "description": "Posted item!",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path), Body: bytes.NewReader([]byte(`{
    "331": {
        "name": "Dark patch 2",
        "locationIndexes": [
            4,
            55,
            394
        ],
        "description": "The second dark patch",
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": ""
        }
    },
    "772": {
        "name": "White spot",
        "locationIndexes": [
            14,
            5,
            94
        ],
        "description": "",
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    },
    "id5": {
        "name": "White spot",
        "locationIndexes": [
            3,
            9,
            199
        ],
        "description": "Posted item!",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path), Body: bytes.NewReader([]byte(`{
    "id6": {
        "name": "White spot",
        "locationIndexes": [
            3,
            9,
            199
        ],
        "description": "Posted item!",
        "imageName": "the_img.png",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"id3", "id4", "id5", "id6"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const postItem = `{
		"name": "White spot",
		"locationIndexes": [ 3, 9, 199 ],
		"description": "Posted item!"
	}`
const postItemWithImageName = `{
		"name": "White spot",
		"imageName": "the_img.png",
		"locationIndexes": [ 3, 9, 199 ],
		"description": "Posted item!"
	}`

const routePath = "/roi/TheDataSetID"

// File not in S3, should work
req, _ := http.NewRequest("POST", routePath, bytes.NewReader([]byte(postItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should work
req, _ = http.NewRequest("POST", routePath, bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File already has an ROI by this name by another user, should work
req, _ = http.NewRequest("POST", routePath, bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File already has an ROI by this name by same user, should FAIL
req, _ = http.NewRequest("POST", routePath, bytes.NewReader([]byte(postItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// With imageName field, should work
req, _ = http.NewRequest("POST", routePath, bytes.NewReader([]byte(postItemWithImageName)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200

200

200

400
ROI name already used: White spot

200
Example (RoiHandler_Put)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path), Body: bytes.NewReader([]byte(`{
    "331": {
        "name": "White spot",
        "locationIndexes": [
            3,
            9,
            199
        ],
        "description": "Updated item!",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    "772": {
        "name": "White spot",
        "locationIndexes": [
            14,
            5,
            94
        ],
        "description": "",
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)

apiRouter := MakeRouter(svcs)

const putItem = `{
		"name": "White spot",
		"locationIndexes": [ 3, 9, 199 ],
		"description": "Updated item!"
	}`

// Put finds file missing, ERROR
req, _ := http.NewRequest("PUT", "/roi/TheDataSetID/331", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Put finds empty file, ERROR
req, _ = http.NewRequest("PUT", "/roi/TheDataSetID/331", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Put cant find item, ERROR
req, _ = http.NewRequest("PUT", "/roi/TheDataSetID/22", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Put with bad name, ERROR
req, _ = http.NewRequest("PUT", "/roi/TheDataSetID/22", bytes.NewReader([]byte(`{
		"name": "",
		"locationIndexes": [ 3, 9, 199 ],
		"description": "Updated item!"
	}`)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Put finds item, OK
req, _ = http.NewRequest("PUT", "/roi/TheDataSetID/331", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Put shared item, ERROR
req, _ = http.NewRequest("PUT", "/roi/TheDataSetID/shared-331", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
ROI 331 not found

404
ROI 331 not found

404
ROI 22 not found

400
Invalid ROI name: ""

200

400
Cannot edit shared ROIs
Example (RoiHandler_Share)
sharedROIContents := `{
    "99": {
        "name": "Shared already",
        "locationIndexes": [33],
        "description": "",
        "shared": true,
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174"
        }
    }
}`

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedROIContents))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedROIContents))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedROIContents))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(roi2XItems))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(sharedROIContents))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(roiSharedS3Path), Body: bytes.NewReader([]byte(`{
    "16": {
        "name": "Dark patch 2",
        "locationIndexes": [
            4,
            55,
            394
        ],
        "description": "The second dark patch",
        "shared": true,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": ""
        }
    },
    "99": {
        "name": "Shared already",
        "locationIndexes": [
            33
        ],
        "description": "",
        "shared": true,
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": ""
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"16"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = ""

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/roi/TheDataSetID/331", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("POST", "/share/roi/TheDataSetID/331", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File missing the id being shared
req, _ = http.NewRequest("POST", "/share/roi/TheDataSetID/333", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains ID, share OK
req, _ = http.NewRequest("POST", "/share/roi/TheDataSetID/331", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
331 not found

404
331 not found

404
333 not found

200
"16"
Example (SpectrumAnnotationHandler_Delete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "25": {
        "eV": 12345,
        "name": "Weird part of spectrum",
        "roiID": "roi123",
        "shared": false,
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path), Body: bytes.NewReader([]byte(`{
    "8": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path), Body: bytes.NewReader([]byte(`{}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
	&s3.PutObjectOutput{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const routePath = "/annotation/rtt-123/3"

// Delete finds file missing, ERROR
req, _ := http.NewRequest("DELETE", routePath, nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds empty file, ERROR
req, _ = http.NewRequest("DELETE", routePath, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete cant find item, ERROR
req, _ = http.NewRequest("DELETE", routePath, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete finds item, OK
req, _ = http.NewRequest("DELETE", "/annotation/rtt-123/5", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item but from wrong user, ERROR
req, _ = http.NewRequest("DELETE", "/annotation/rtt-123/shared-5", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Delete shared item, OK
req, _ = http.NewRequest("DELETE", "/annotation/rtt-123/shared-25", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
3 not found

404
3 not found

404
3 not found

200

401
5 not owned by 600f2a0806b6c70071d3d174

200
Example (SpectrumAnnotationHandler_Get)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// File not in S3, should return 404
req, _ := http.NewRequest("GET", "/annotation/rtt-123/8", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File in S3 empty, should return 404
req, _ = http.NewRequest("GET", "/annotation/rtt-123/8", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains stuff, using ID thats not in there, should return 404
req, _ = http.NewRequest("GET", "/annotation/rtt-123/6", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains stuff, using ID that exists
req, _ = http.NewRequest("GET", "/annotation/rtt-123/8", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Check that shared file was loaded if shared ID sent in
req, _ = http.NewRequest("GET", "/annotation/rtt-123/shared-8", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
8 not found

404
8 not found

404
6 not found

200
{
    "name": "Left of spectrum",
    "roiID": "roi123",
    "eV": 555,
    "shared": false,
    "creator": {
        "name": "Peter",
        "user_id": "u123",
        "email": "niko@spicule.co.uk"
    }
}

200
{
    "name": "Left of spectrum",
    "roiID": "roi123",
    "eV": 555,
    "shared": true,
    "creator": {
        "name": "Peter",
        "user_id": "u123",
        "email": "niko@spicule.co.uk"
    }
}
Example (SpectrumAnnotationHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"93": {
		"eV": 20000,
		"name": "right of spectrum",
		"roiID": "roi111",
		"shared": true,
		"creator": { "name": "Tom", "user_id": "u124", "email": "" }
	}
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/annotation/rtt-123", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/annotation/rtt-123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/annotation/rtt-123", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{}

200
{}

200
{
    "5": {
        "name": "Weird part of spectrum",
        "roiID": "roi123",
        "eV": 12345,
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": "niko@spicule.co.uk"
        }
    },
    "8": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    },
    "shared-93": {
        "name": "right of spectrum",
        "roiID": "roi111",
        "eV": 20000,
        "shared": true,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": ""
        }
    }
}
Example (SpectrumAnnotationHandler_Post)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
}
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path), Body: bytes.NewReader([]byte(`{
    "id1": {
        "name": "The modified flag",
        "roiID": "roi222",
        "eV": 9999,
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path), Body: bytes.NewReader([]byte(`{
    "id2": {
        "name": "The modified flag",
        "roiID": "roi222",
        "eV": 9999,
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path), Body: bytes.NewReader([]byte(`{
    "5": {
        "name": "Weird part of spectrum",
        "roiID": "roi123",
        "eV": 12345,
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": "niko@spicule.co.uk"
        }
    },
    "8": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    },
    "id3": {
        "name": "The modified flag",
        "roiID": "roi222",
        "eV": 9999,
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
	&s3.PutObjectOutput{},
	&s3.PutObjectOutput{},
}

var idGen MockIDGenerator
idGen.ids = []string{"id1", "id2", "id3"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

body := `{
	"name": "The modified flag",
	"roiID": "roi222",
	"eV": 9999
}`

req, _ := http.NewRequest("POST", "/annotation/rtt-123", bytes.NewReader([]byte(body)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/annotation/rtt-123", bytes.NewReader([]byte(body)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/annotation/rtt-123", bytes.NewReader([]byte(body)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
{
    "id1": {
        "name": "The modified flag",
        "roiID": "roi222",
        "eV": 9999,
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}

200
{
    "id2": {
        "name": "The modified flag",
        "roiID": "roi222",
        "eV": 9999,
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}

200
{
    "5": {
        "name": "Weird part of spectrum",
        "roiID": "roi123",
        "eV": 12345,
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": "niko@spicule.co.uk"
        }
    },
    "8": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    },
    "id3": {
        "name": "The modified flag",
        "roiID": "roi222",
        "eV": 9999,
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}
Example (SpectrumAnnotationHandler_Put)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path), Body: bytes.NewReader([]byte(`{
    "5": {
        "name": "Updated Item",
        "roiID": "roi444",
        "eV": 8888,
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": "niko@spicule.co.uk"
        }
    },
    "8": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "name": "Updated Item",
    "roiID": "roi444",
    "eV": 8888
}`

const routePath = "/annotation/rtt-123/3"

// File not in S3, should work
req, _ := http.NewRequest("PUT", routePath, bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should work
req, _ = http.NewRequest("PUT", routePath, bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// ROI annotations for this exist, but we're adding a new annotation
req, _ = http.NewRequest("PUT", routePath, bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// ROI annotations for this exist, but we're editing an existing annotation
req, _ = http.NewRequest("PUT", "/annotation/rtt-123/5", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
3 not found

404
3 not found

404
3 not found

200
{
    "5": {
        "name": "Updated Item",
        "roiID": "roi444",
        "eV": 8888,
        "shared": false,
        "creator": {
            "name": "Tom",
            "user_id": "u124",
            "email": "niko@spicule.co.uk"
        }
    },
    "8": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": false,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    }
}
Example (SpectrumAnnotationHandler_Share)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationS3Path),
	},
	// Reading shared file to add to it
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(annotations2x))),
	},
	// Shared file
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "25": {
        "eV": 12345,
        "roiID": "roi123",
        "name": "Weird part of spectrum",
        "shared": true,
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
}`))),
	},
}
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(annotationSharedS3Path), Body: bytes.NewReader([]byte(`{
    "25": {
        "name": "Weird part of spectrum",
        "roiID": "roi123",
        "eV": 12345,
        "shared": true,
        "creator": {
            "name": "The user who shared",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    "83": {
        "name": "Left of spectrum",
        "roiID": "roi123",
        "eV": 555,
        "shared": true,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
}

var idGen MockIDGenerator
idGen.ids = []string{"83"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = ""

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/annotation/rtt-123/8", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("POST", "/share/annotation/rtt-123/8", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File missing the id being shared
req, _ = http.NewRequest("POST", "/share/annotation/rtt-123/7", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File contains ID, share OK
req, _ = http.NewRequest("POST", "/share/annotation/rtt-123/8", bytes.NewReader([]byte(putItem)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
8 not found

404
8 not found

404
7 not found

200
"83"
Example (Subscriptions)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(userJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(userJSON))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{

		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"), Body: bytes.NewReader([]byte(userJSON)),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)

apiRouter := MakeRouter(svcs)

jsonstr := `{"topics": [{
			"name": "topic c",
			"config": {
				"method": {
					"ui": true,
					"sms": false,
					"email": false
				}
			}
		}, {
			"name": "topic d",
			"config": {
				"method": {
					"ui": true,
					"sms": false,
					"email": false
				}
			}
		}]}`
req, _ := http.NewRequest("POST", "/notification/subscriptions", bytes.NewReader([]byte(jsonstr)))
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)

req, _ = http.NewRequest("GET", "/notification/subscriptions", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)
Output:

ensure-valid: 200
{
    "topics": [
        {
            "name": "topic c",
            "config": {
                "method": {
                    "ui": true,
                    "sms": false,
                    "email": false
                }
            }
        },
        {
            "name": "topic d",
            "config": {
                "method": {
                    "ui": true,
                    "sms": false,
                    "email": false
                }
            }
        }
    ]
}
ensure-valid: 200
{
    "topics": [
        {
            "name": "topic c",
            "config": {
                "method": {
                    "ui": true,
                    "sms": false,
                    "email": false
                }
            }
        },
        {
            "name": "topic d",
            "config": {
                "method": {
                    "ui": true,
                    "sms": false,
                    "email": false
                }
            }
        }
    ]
}
Example (Subscriptions_empty)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"), Body: bytes.NewReader([]byte(emptyUserJSON)),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)

apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/notification/subscriptions", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-valid: %v\n", resp.Code)
fmt.Printf("%v", resp.Body)
Output:

ensure-valid: 200
{
    "topics": []
}
Example (TestLoggingDebug)
testServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer testServer.Close()
//"Component":"http://example.com/foo","Message":"{\"alive\": true}","Version":"","Params":{"method":"GET"},"Environment":"unit-test","User":"myuserid"}
var ExpIndexObject = []string{
	`{"Instance":"","Time":"0000-00-00T00:00:00-00:00","Component":"http://example.com/foo","Message":"{\"alive\": true}","Version":"","Params":{"method":"GET"},"Environment":"unit-test","User":"myuserid"}`,
}
var ExpRespObject = []string{
	`{"_index":"metrics","_type":"trigger","_id":"B0tzT3wBosV6bFs8gJvY","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":8468,"_primary_term":1}`,
	`{"_index":"metrics","_type":"trigger","_id":"B0tzT3wBosV6bFs8gJvY","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":8468,"_primary_term":1}`,
}

var adjtime = "0000-00-00T00:00:00-00:00"
d := esutil.DummyElasticClient{}
foo, err := d.DummyElasticSearchClient(testServer.URL, ExpRespObject, ExpIndexObject, ExpRespObject, &adjtime)

apiConfig := config.APIConfig{EnvironmentName: "Test"}
if err != nil {
	fmt.Printf("%v\n", err)
}
connection, err := esutil.Connect(foo, apiConfig)

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/myuserid.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"userid":"myuserid","notifications":{"topics":[],"hints":["point-select-alt","point-select-z-for-zoom","point-select-shift-for-pan","lasso-z-for-zoom","lasso-shift-for-pan","dwell-exists-test-fm-5x5-full","dwell-exists-069927431"],"uinotifications":[]},"userconfig":{"name":"peternemere","email":"peternemere@gmail.com","cell":"","data_collection":"1.0"}}`)))},
}

s := MakeMockSvcs(&mockS3, nil, nil, &connection, nil)

mockvalidator := api.MockJWTValidator{}
l := LoggerMiddleware{
	APIServices:  &s,
	JwtValidator: &mockvalidator,
}

handler := func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")

	// In the future we could report back on the status of our DB, or our cache
	// (e.g. Redis) by performing a simple PING, and include them in the response.
	io.WriteString(w, `{"alive": true}`)
}

req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)

fmt.Printf("%d - %s", w.Code, w.Body.String())

h := http.HandlerFunc(handler)
handlerToTest := l.Middleware(h)

handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
Output:

200 - {"alive": true}&map[]
Example (TestLoggingInfo)
testServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer testServer.Close()
//"Component":"http://example.com/foo","Message":"{\"alive\": true}","Version":"","Params":{"method":"GET"},"Environment":"unit-test","User":"myuserid"}
var ExpIndexObject = []string{
	`{"Instance":"","Time":"0000-00-00T00:00:00-00:00","Component":"http://example.com/foo","Message":"{\"alive\": true}","Version":"","Params":{"method":"GET"},"Environment":"unit-test","User":"myuserid"}`,
}
var ExpRespObject = []string{
	`{"_index":"metrics","_type":"trigger","_id":"B0tzT3wBosV6bFs8gJvY","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":8468,"_primary_term":1}`,
	`{"_index":"metrics","_type":"trigger","_id":"B0tzT3wBosV6bFs8gJvY","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":8468,"_primary_term":1}`,
}

var adjtime = "0000-00-00T00:00:00-00:00"
d := esutil.DummyElasticClient{}
foo, err := d.DummyElasticSearchClient(testServer.URL, ExpRespObject, ExpIndexObject, ExpRespObject, &adjtime)

apiConfig := config.APIConfig{EnvironmentName: "Test"}
if err != nil {
	fmt.Printf("%v\n", err)
}
connection, err := esutil.Connect(foo, apiConfig)

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/myuserid.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"userid":"myuserid","notifications":{"topics":[],"hints":["point-select-alt","point-select-z-for-zoom","point-select-shift-for-pan","lasso-z-for-zoom","lasso-shift-for-pan","dwell-exists-test-fm-5x5-full","dwell-exists-069927431"],"uinotifications":[]},"userconfig":{"name":"peternemere","email":"peternemere@gmail.com","cell":"","data_collection":"1.0"}}`)))},
}

var ll = logger.LogInfo

s := MakeMockSvcs(&mockS3, nil, nil, &connection, &ll)

mockvalidator := api.MockJWTValidator{}
l := LoggerMiddleware{
	APIServices:  &s,
	JwtValidator: &mockvalidator,
}

handler := func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")

	// In the future we could report back on the status of our DB, or our cache
	// (e.g. Redis) by performing a simple PING, and include them in the response.
	io.WriteString(w, `{"alive": true}`)
}

req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)

fmt.Printf("%d - %s", w.Code, w.Body.String())

h := http.HandlerFunc(handler)
handlerToTest := l.Middleware(h)

handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
Output:

200 - {"alive": true}&map[]
Example (UserManagementUserQuery_And_UserGet)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/user/query", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("query: %v\n", resp.Code)

var users []auth0UserInfo
err := json.Unmarshal(resp.Body.Bytes(), &users)

fmt.Printf("%v|%v\n", err, len(users) > 0 && seemsValid(users[0]))

req, _ = http.NewRequest("GET", "/user/by-id/"+knownTestUserID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("by-id: %v\n", resp.Code)

var user auth0UserInfo
err = json.Unmarshal(resp.Body.Bytes(), &user)

fmt.Printf("%v|%v\n", err, seemsValid(user))
Output:

query: 200
<nil>|true
by-id: 200
<nil>|true
Example (UserManagement_AddDeleteRole)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)
apiRouter := MakeRouter(svcs)

// To test add/delete of roles:

// In case test ran before, we call delete first, then list roles, ensuring it's not in there.
req, _ := http.NewRequest("DELETE", "/user/roles/"+knownTestUserID+"/"+knownTestRoleID, nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-del: %v\n", resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/user/roles/"+knownTestUserID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("check-del: %v\n", resp.Code)
fmt.Println(resp.Body)

// We then add the role, list roles, ensure it's there
req, _ = http.NewRequest("POST", "/user/roles/"+knownTestUserID+"/"+knownTestRoleID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("add: %v\n", resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/user/roles/"+knownTestUserID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-added: %v\n", resp.Code)
fmt.Println(resp.Body)

// Finally, delete role, list roles, ensure it's gone
req, _ = http.NewRequest("DELETE", "/user/roles/"+knownTestUserID+"/"+knownTestRoleID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("delete: %v\n", resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("GET", "/user/roles/"+knownTestUserID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("ensure-del-2: %v\n", resp.Code)
fmt.Println(resp.Body)

req, _ = http.NewRequest("POST", "/user/roles/"+knownTestUserID+"/"+knownTestRoleID, nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Printf("add-back: %v\n", resp.Code)
fmt.Println(resp.Body)
Output:

ensure-del: 200

check-del: 200
null

add: 200

ensure-added: 200
[
    {
        "id": "rol_KdjHrTCteclbY7om",
        "name": "No Permissions",
        "description": "When a user has signed up and we don't know who they are, we assign this."
    }
]

delete: 200

ensure-del-2: 200
null

add-back: 200
Example (UserManagement_Roles_And_UserByRole)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
setTestAuth0Config(&svcs)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/user/all-roles", nil)
resp := executeRequest(req, apiRouter.Router)

// Expect to parse this thing, and get SOME roles back
var roles []roleInfo
err := json.Unmarshal(resp.Body.Bytes(), &roles)

fmt.Println(resp.Code)
fmt.Printf("%v|%v\n", err, len(roles) > 0 && len(roles[0].ID) > 0 && len(roles[0].Name) > 0 && len(roles[0].Description) > 0)

// request users for first role, hopefully we have some assigned, else test will fail!
if len(roles) > 0 {
	req, _ = http.NewRequest("GET", "/user/by-role/"+roles[0].ID, nil)
	resp = executeRequest(req, apiRouter.Router)

	var users []auth0UserInfo
	err = json.Unmarshal(resp.Body.Bytes(), &users)

	fmt.Println(resp.Code)
	fmt.Printf("%v|%v\n", err, len(users) > 0 && seemsValid(users[0]))
}
Output:

200
<nil>|true
200
<nil>|true
Example (User_config)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

userJSON := `{"userid":"600f2a0806b6c70071d3d174","notifications":{"topics":[{"name":"test-dataset-available","config":{"method":{"ui":true,"sms":true,"email":true}}}],"hints":["hint a","hint b"],"uinotifications":null},"userconfig":{"name":"Niko Bellic","email":"niko@spicule.co.uk","cell":"+123456789","data_collection":"true"}}`
userUpdatedJSON := `{"userid":"600f2a0806b6c70071d3d174","notifications":{"topics":[{"name":"test-dataset-available","config":{"method":{"ui":true,"sms":true,"email":true}}}],"hints":["hint a","hint b"],"uinotifications":null},"userconfig":{"name":"Niko Bellic","email":"niko@spicule.co.uk","cell":"+123456789","data_collection":"false"}}`

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(userJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(userJSON))),
	},
}

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	s3.PutObjectInput{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/600f2a0806b6c70071d3d174.json"), Body: bytes.NewReader([]byte(userUpdatedJSON)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	&s3.PutObjectOutput{},
}
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)

setTestAuth0Config(&svcs)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/user/config", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(fmt.Sprintf("ensure-valid: %v", resp.Code))
fmt.Println(resp.Body)

j := `{"name": "Niko Bellic", "email": "niko@spicule.co.uk","cell": "+123456789","data_collection": "false"}`

req, _ = http.NewRequest("POST", "/user/config", bytes.NewReader([]byte(j)))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(fmt.Sprintf("ensure-valid: %v", resp.Code))
fmt.Println(resp.Body)
Output:

ensure-valid: 200
{
    "name": "Niko Bellic",
    "email": "niko@spicule.co.uk",
    "cell": "+123456789",
    "data_collection": "true"
}

ensure-valid: 200
{
    "name": "Niko Bellic",
    "email": "niko@spicule.co.uk",
    "cell": "+123456789",
    "data_collection": "false"
}
Example (Version)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/piquant-version.json"),
	},
	{
		Bucket: aws.String(ConfigBucketForUnitTest), Key: aws.String("PixliseConfig/piquant-version.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"version": "registry.github.com/pixlise/piquant/runner:3.2.8",
	"changedUnixTimeSec": 1630635994,
	"creator": {
		"name": "Niko Belic",
		"user_id": "12345",
		"email": "nikobellic@gmail.com"
	}
}`))),
	},
}
svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(strings.HasPrefix(string(resp.Body.Bytes()), "<!DOCTYPE html>"))

versionPat := regexp.MustCompile(`<h1>PIXLISE API</h1><p>Version .+</p>`)
fmt.Println(versionPat.MatchString(string(resp.Body.Bytes())))

req, _ = http.NewRequest("GET", "/version", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Printf("%v\n", resp.Body.String())

req, _ = http.NewRequest("GET", "/version", nil)
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)

if resp.Code != 200 {
	fmt.Printf("%v\n", resp.Body.String())
}

var ver ComponentVersionsGetResponse
err := json.Unmarshal(resp.Body.Bytes(), &ver)
fmt.Printf("%v\n", err)
if err == nil {
	// Print out how many version structs and if we find an "API" one
	foundAPI := false
	foundPIQUANT := false
	for _, v := range ver.Components {
		if v.Component == "API" {
			foundAPI = true
		}
		if v.Component == "PIQUANT" {
			foundPIQUANT = true

			// Check it's what we expected
			fmt.Printf("PIQUANT version: %v\n", v.Version)
		}
	}

	vc := "not enough"
	if len(ver.Components) > 1 {
		vc = "ok"
	}
	fmt.Printf("Version count: %v, API found: %v, PIQUANT found: %v\n", vc, foundAPI, foundPIQUANT)
}
Output:

200
true
true
500
PIQUANT version not set

200
<nil>
PIQUANT version: runner:3.2.8
Version count: ok, API found: true, PIQUANT found: true
Example (ViewStateHandler_Delete)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// DELETE not implemented! Should return 405
req, _ := http.NewRequest("DELETE", "/view-state/TheDataSetID/widget", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405
Example (ViewStateHandler_DeleteCollection)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/viewstate123.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/viewstate555.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewStateIDs": ["view one", "view state 2", "num 3 view"], "name": "viewState555"}`))),
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/viewstate555.json"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("DELETE", "/view-state/collections/TheDataSetID/viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, success
req, _ = http.NewRequest("DELETE", "/view-state/collections/TheDataSetID/viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
View state collection not found

200
Example (ViewStateHandler_DeleteCollectionShared)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "WorkspaceCollections/viewstate123.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "WorkspaceCollections/viewstate555.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"viewStateIDs": ["view one", "view state 2"],
				"name": "viewstate123",
				"shared": true,
				"creator": {
					"name": "Roman Bellic",
					"user_id": "another-user-123",
					"email": "roman@spicule.co.uk"
				}
			}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"viewStateIDs": ["view one", "view state 2", "num 3 view"],
				"name": "viewState555",
				"shared": true,
				"creator": {
					"name": "Niko Bellic",
					"user_id": "600f2a0806b6c70071d3d174",
					"email": "niko@spicule.co.uk"
				}
			}`))),
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "WorkspaceCollections/viewstate555.json"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Not created by user, should fail
req, _ := http.NewRequest("DELETE", "/view-state/collections/TheDataSetID/shared-viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Created by user, success
req, _ = http.NewRequest("DELETE", "/view-state/collections/TheDataSetID/shared-viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

401
viewstate123 not owned by 600f2a0806b6c70071d3d174

200
Example (ViewStateHandler_DeleteSaved)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

collectionRoot := "UserContent/600f2a0806b6c70071d3d174/TheDataSetID/ViewState/WorkspaceCollections"

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 1
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate123.json"),
	},

	// Test 2: no collections
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},

	// Test 3: not in collections
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(collectionRoot + "/a collection.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(collectionRoot + "/Another-Collection.json"),
	},

	// Test 4: found in collection
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(collectionRoot + "/culprit.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	// Test 1: no view state file
	nil,

	// Test 2: exists
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "viewstate555", "viewState": {"selection": {}}}`))),
	},

	// Test 3: exists + collections returned
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "viewstate555", "viewState": {"selection": {}}}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "a collection", "viewStateIDs": ["some view state", "another"]}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "Another-Collection", "viewStateIDs": ["also not the one"]}`))),
	},

	// Test 4: exists + collections returned, one contains this view state!
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "viewstate555", "viewState": {"selection": {}}}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "culprit", "viewStateIDs": ["some view state", "viewstate555", "another"]}`))),
	},
}

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(collectionRoot),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(collectionRoot),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(collectionRoot),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	// Test 2: no collections
	{
		Contents: []*s3.Object{},
	},
	{
		Contents: []*s3.Object{
			{Key: aws.String(collectionRoot + "/a collection.json"), LastModified: aws.Time(time.Unix(1634731920, 0))},
			{Key: aws.String(collectionRoot + "/Another-Collection.json"), LastModified: aws.Time(time.Unix(1634731921, 0))},
		},
	},
	{
		Contents: []*s3.Object{
			{Key: aws.String(collectionRoot + "/culprit.json"), LastModified: aws.Time(time.Unix(1634731922, 0))},
			{Key: aws.String(collectionRoot + "/Another-Collection.json"), LastModified: aws.Time(time.Unix(1634731923, 0))},
		},
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("DELETE", "/view-state/saved/TheDataSetID/viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, no collections, success
req, _ = http.NewRequest("DELETE", "/view-state/saved/TheDataSetID/viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, collections checked (not in there), success
req, _ = http.NewRequest("DELETE", "/view-state/saved/TheDataSetID/viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists but is in a collection, fail
req, _ = http.NewRequest("DELETE", "/view-state/saved/TheDataSetID/viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
viewstate123 not found

200

200

409
Workspace "viewstate555" is in collection "culprit". Please delete the workspace from all collections before before trying to delete it.
Example (ViewStateHandler_DeleteSavedShared)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 1: not owned by user
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "Workspaces/viewstate123.json"),
	},

	// Test 2: owned by user
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "Workspaces/viewstate555.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"name": "viewstate123",
				"viewState": {"selection": {}},
				"shared": true,
				"creator": {
					"name": "Roman Bellic",
					"user_id": "another-user-123",
					"email": "roman@spicule.co.uk"
				}
			}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"name": "viewstate555",
				"viewState": {"selection": {}},
				"shared": true,
				"creator": {
					"name": "Niko Bellic",
					"user_id": "600f2a0806b6c70071d3d174",
					"email": "niko@spicule.co.uk"
				}
			}`))),
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "Workspaces/viewstate555.json"),
	},
}

mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Not owned by user, should fail
req, _ := http.NewRequest("DELETE", "/view-state/saved/TheDataSetID/shared-viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, owned by user, success
req, _ = http.NewRequest("DELETE", "/view-state/saved/TheDataSetID/shared-viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

401
viewstate123 not owned by 600f2a0806b6c70071d3d174

200
Example (ViewStateHandler_Get)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("GET", "/view-state/TheDataSetID/widget", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405
Example (ViewStateHandler_GetCollection)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/The 1st one.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/Another_collection-01-01-2022.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/State one.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/The end.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/Collection with creator.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/State one.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/The end.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "Another_collection-01-01-2022",
    "viewStateIDs": [
        "State one",
        "The end"
    ],
	"description": "some description",
    "viewStates": null
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"quantification": {"appliedQuantID": "quant for state one"}}}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"quantification": {"appliedQuantID": "quant for the end"}}}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "Another_collection-01-01-2022",
    "viewStateIDs": [
        "State one",
        "The end"
    ],
    "description": "some description",
    "viewStates": null,
    "shared": false,
    "creator": {
        "name": "Roman Bellic",
        "user_id": "another-user-123",
        "email": "roman@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"viewState": {
		"quantification": {
			"appliedQuantID": "quant for state one"
		},
		"selection": {
			"locIdxs": [1, 2],
			"pixelSelectionImageName": "file.tif",
			"pixelIdxs": [3,4]
		}
	}
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"quantification": {"appliedQuantID": "quant for the end"}}}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("GET", "/view-state/collections/TheDataSetID/The 1st one", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists (no creator info saved), success
req, _ = http.NewRequest("GET", "/view-state/collections/TheDataSetID/Another_collection-01-01-2022", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists (with creator info), success
req, _ = http.NewRequest("GET", "/view-state/collections/TheDataSetID/Collection with creator", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
The 1st one not found

200
{
    "viewStateIDs": [
        "State one",
        "The end"
    ],
    "name": "Another_collection-01-01-2022",
    "description": "some description",
    "viewStates": {
        "State one": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for state one"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        },
        "The end": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for the end"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        }
    }
}

200
{
    "viewStateIDs": [
        "State one",
        "The end"
    ],
    "name": "Another_collection-01-01-2022",
    "description": "some description",
    "viewStates": {
        "State one": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for state one"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": [
                    1,
                    2
                ],
                "pixelSelectionImageName": "file.tif",
                "pixelIdxs": [
                    3,
                    4
                ]
            }
        },
        "The end": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for the end"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        }
    },
    "shared": false,
    "creator": {
        "name": "Roman Bellic",
        "user_id": "another-user-123",
        "email": "roman@spicule.co.uk"
    }
}
Example (ViewStateHandler_GetCollectionShared)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "WorkspaceCollections/The 1st one.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "WorkspaceCollections/The one that works.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "The one that works",
    "viewStateIDs": [
        "State one",
        "The end"
    ],
	"description": "some description",
    "shared": true,
    "creator": {
        "name": "Roman Bellic",
        "user_id": "another-user-123",
        "email": "roman@spicule.co.uk"
    },
    "viewStates": {
        "State one": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for state one"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        },
        "The end": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for the end"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        }
    }
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("GET", "/view-state/collections/TheDataSetID/shared-The 1st one", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, success
req, _ = http.NewRequest("GET", "/view-state/collections/TheDataSetID/shared-The one that works", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
The 1st one not found

200
{
    "viewStateIDs": [
        "State one",
        "The end"
    ],
    "name": "The one that works",
    "description": "some description",
    "viewStates": {
        "State one": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for state one"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        },
        "The end": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant for the end"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        }
    },
    "shared": true,
    "creator": {
        "name": "Roman Bellic",
        "user_id": "another-user-123",
        "email": "roman@spicule.co.uk"
    }
}
Example (ViewStateHandler_GetReferencedIDs)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 1
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/331.json"),
	},
	// Test 2
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/332.json"),
	},
	// Test 3
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/333.json"),
	},
	// Getting ROIs
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/TheDataSetID/ROI.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/TheDataSetID/ROI.json"),
	},
	// Getting expressions
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/DataExpressions.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/DataExpressions.json"),
	},
	// Getting rgb mixes
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/RGBMixes.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/RGBMixes.json"),
	},
	// Getting quant config
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/TheDataSetID/Quantifications/summary-quant123.json"),
	},
}

mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`}`))),
	},
	{
		// View state that references non-shared IDs. We want to make sure it returns the right ones and
		// count, so we return multiple IDs here:
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				 "viewState": {
					 "contextImages": { "0": { "mapLayers": [ { "expressionID": "rgbmix-123", "opacity": 1, "visible": true } ] } },
					 "quantification": {"appliedQuantID": "quant123"},
					 "binaryPlots": { "44": { "expressionIDs": ["shared-expr", "expr1"], "visibleROIs": ["shared-roi"] } },
					 "ternaryPlots": { "66": { "expressionIDs": ["shared-expr2"], "visibleROIs": ["roi2"] } }
				 },
				 "name": "333",
				"description": "the description of 333"
			}`))),
	},
	// ROIs
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"roi2": {
		"name": "Dark patch 2",
		"description": "The second dark patch",
		"locationIndexes": [4, 55, 394],
		"creator": { "name": "Peter", "user_id": "u123" }
	}
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"roi": {
		"name": "Shared patch 2",
		"description": "The shared patch",
		"locationIndexes": [4, 55, 394],
		"creator": { "name": "PeterN", "user_id": "u123" }
	}
}`))),
	},
	// Expressions
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"abc123": {
		"name": "Temp data",
		"expression": "housekeeping(\"something\")",
		"type": "All",
		"comments": "comments for abc123 expression",
		"creator": {
			"user_id": "444",
			"name": "Niko",
			"email": "niko@spicule.co.uk"
		}
	},
	"expr1": {
		"name": "Calcium weight%",
		"expression": "element(\"Ca\", \"%\")",
		"type": "All",
		"comments": "comments for expr1",
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "peter@spicule.co.uk"
		}
	}
}`))),
	},
	nil, // simulate missing shared expressions file...
	// User RGB mixes
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"123": {
		"name": "Ca-Ti-Al ratios",
		"red": {
			"expressionID": "expr-for-Ca",
			"rangeMin": 1.5,
			"rangeMax": 4.3
		},
		"green": {
			"expressionID": "expr-for-Al",
			"rangeMin": 2.5,
			"rangeMax": 5.3
		},
		"blue": {
			"expressionID": "expr-for-Ti",
			"rangeMin": 3.5,
			"rangeMax": 6.3
		},
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "niko@spicule.co.uk"
		}
	}
}`))),
	},
	// Shared RGB mixes
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"380": {
		"name": "Fe-Ca-Al ratios",
		"red": {
			"expressionID": "expr-for-Fe",
			"rangeMin": 2.5,
			"rangeMax": 4.3
		},
		"green": {
			"expressionID": "expr-for-Ca",
			"rangeMin": 3.5,
			"rangeMax": 5.3
		},
		"blue": {
			"expressionID": "expr-for-Ti",
			"rangeMin": 3.5,
			"rangeMax": 6.3
		},
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "niko@spicule.co.uk"
		}
	}
}`))),
	},
	// Quant summary
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"shared": false,
	"params": {
		"pmcsCount": 93,
		"name": "my test quant",
		"dataBucket": "dev-pixlise-data",
		"datasetPath": "Datasets/rtt-456/5x5dataset.bin",
		"datasetID": "rtt-456",
		"jobBucket": "dev-pixlise-piquant-jobs",
		"detectorConfig": "PIXL",
		"elements": [
			"Sc",
			"Cr"
		],
		"parameters": "-q,pPIETXCFsr -b,0,12,60,910,280,16",
		"runTimeSec": 120,
		"coresPerNode": 6,
		"startUnixTime": 1589948988,
		"creator": {
			"name": "peternemere",
			"user_id": "600f2a0806b6c70071d3d174",
            "email": ""
		},
		"roiID": "ZcH49SYZ",
		"elementSetID": "",
		"quantMode": "AB"
	},
	"jobId": "quant123",
	"status": "complete",
	"message": "Nodes ran: 1",
	"endUnixTime": 1589949035,
	"outputFilePath": "UserContent/user-1/rtt-456/Quantifications",
	"piquantLogList": [
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_stdout.log",
		"https://dev-pixlise-piquant-jobs.s3.us-east-1.amazonaws.com/Jobs/UC2Bchyz/piquant-logs/node00001.pmcs_threads.log"
	]
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// User file not there, should say not found
req, _ := http.NewRequest("GET", "/view-state/saved/TheDataSetID/331/references", bytes.NewReader([]byte{}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID/332/references", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Gets mix of shared and not shared IDs
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID/333/references", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
331 not found

404
332 not found

200
{
    "quant": {
        "id": "quant123",
        "name": "my test quant",
        "creator": {
            "name": "peternemere",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": ""
        }
    },
    "ROIs": [
        {
            "id": "roi2",
            "name": "Dark patch 2",
            "creator": {
                "name": "Peter",
                "user_id": "u123",
                "email": ""
            }
        },
        {
            "id": "shared-roi",
            "name": "Shared patch 2",
            "creator": {
                "name": "PeterN",
                "user_id": "u123",
                "email": ""
            }
        }
    ],
    "expressions": [
        {
            "id": "expr1",
            "name": "Calcium weight%",
            "creator": {
                "name": "Peter N",
                "user_id": "999",
                "email": "peter@spicule.co.uk"
            }
        },
        {
            "id": "shared-expr",
            "name": "",
            "creator": {
                "name": "",
                "user_id": "",
                "email": ""
            }
        },
        {
            "id": "shared-expr2",
            "name": "",
            "creator": {
                "name": "",
                "user_id": "",
                "email": ""
            }
        }
    ],
    "rgbMixes": [
        {
            "id": "rgbmix-123",
            "name": "",
            "creator": {
                "name": "",
                "user_id": "",
                "email": ""
            }
        }
    ],
    "nonSharedCount": 3
}
Example (ViewStateHandler_GetSaved)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate123.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "bottomWidgetSelectors": []
        },
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant111"
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        },
    "name": "",
    "description": ""
}
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("GET", "/view-state/saved/TheDataSetID/viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, success
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID/viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
viewstate123 not found

200
{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 0,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {},
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {},
        "binaryPlots": {},
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant111"
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        }
    },
    "name": "",
    "description": ""
}
Example (ViewStateHandler_GetSavedShared)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "Workspaces/viewstate123.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "Workspaces/viewstate555.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "bottomWidgetSelectors": []
        },
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant111"
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        },
    "name": "",
    "description": ""
}
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("GET", "/view-state/saved/TheDataSetID/shared-viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, success
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID/shared-viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
viewstate123 not found

200
{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 0,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {},
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {},
        "binaryPlots": {},
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant111"
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        }
    },
    "name": "",
    "description": ""
}
Example (ViewStateHandler_GetSaved_ROIQuantFallbackCheck)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate123.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "bottomWidgetSelectors": []
        },
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "",
            "quantificationByROI": {
                "roi22": "quant222",
                "roi88": "quant333"
            }
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        },
    "name": "",
    "description": ""
}
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Doesn't exist, should fail
req, _ := http.NewRequest("GET", "/view-state/saved/TheDataSetID/viewstate123", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, success
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID/viewstate555", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// TODO: fix this, sometimes this can result in last quant being quant333, likely due to some map reading ordering issue
Output:

404
viewstate123 not found

200
{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 0,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {},
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {},
        "binaryPlots": {},
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant222"
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        }
    },
    "name": "",
    "description": ""
}
Example (ViewStateHandler_List)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

const contextImgJSON = `
	"zoomX": 2,
	"showPoints": true,
	"showPointBBox": false,
	"pointColourScheme": "BW",
	"pointBBoxColourScheme": "PURPLE_CYAN",
	"contextImageSmoothing": "linear",
    "mapLayers": [
        {
			"expressionID": "Fe",
			"opacity": 0.3,
			"visible": true,
			"displayValueRangeMin": 12,
			"displayValueRangeMax": 48.8,
			"displayValueShading": "SHADE_VIRIDIS"
        }
	],
	"roiLayers": [
		{
			"roiID": "roi123",
			"opacity": 0.7,
			"visible": true
		}
	],
	"contextImage": "file1.jpg",
	"elementRelativeShading": true
}`

// Single request results in loading multiple files from S3. First it gets a directory listing...
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateS3Path),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateS3Path + "not-a-widget.json")}, // Not a recognised file name
			{Key: aws.String(viewStateS3Path + "spectrum.json")},
			{Key: aws.String(viewStateS3Path + "spectrum.txt")}, // Not right extension
			{Key: aws.String(viewStateS3Path + "contextImage-map.json")},
			{Key: aws.String(viewStateS3Path + "contextImage-analysis.json")},
			{Key: aws.String(viewStateS3Path + "contextImage-engineering.json")},
			{Key: aws.String(viewStateS3Path + "quantification.json")},
			{Key: aws.String(viewStateS3Path + "selection.json")},
			{Key: aws.String(viewStateS3Path + "roi.json")},
			{Key: aws.String(viewStateS3Path + "analysisLayout.json")},
			{Key: aws.String(viewStateS3Path + "histogram-1.json")},
			{Key: aws.String(viewStateS3Path + "chord-0.json")},
			{Key: aws.String(viewStateS3Path + "chord-1.json")},
			{Key: aws.String(viewStateS3Path + "table-undercontext.json")},
			{Key: aws.String(viewStateS3Path + "table-underspectrum0.json")},
			{Key: aws.String(viewStateS3Path + "binary-underspectrum0.json")},
			{Key: aws.String(viewStateS3Path + "ternary-underspectrum2.json")},
			{Key: aws.String(viewStateS3Path + "variogram-abc123.json")},
			{Key: aws.String(viewStateS3Path + "rgbuImages-33.json")},
			{Key: aws.String(viewStateS3Path + "rgbuPlot-underspectrum1.json")},
			{Key: aws.String(viewStateS3Path + "parallelogram-55.json")},
			{Key: aws.String(viewStateS3Path + "roiQuantTable-ttt.json")},
			{Key: aws.String(viewStateS3Path + "spectrum-top1.json")}, // the "new style" version that comes with a position id
		},
	},
}

// Some of our files are empty, not there, have content
// and they're meant to end up combined into one response...
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "spectrum.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "contextImage-map.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "contextImage-analysis.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "contextImage-engineering.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "quantification.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "selection.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "roi.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "analysisLayout.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "histogram-1.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "chord-0.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "chord-1.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "table-undercontext.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "table-underspectrum0.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "binary-underspectrum0.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "ternary-underspectrum2.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "variogram-abc123.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "rgbuImages-33.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "rgbuPlot-underspectrum1.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "parallelogram-55.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "roiQuantTable-ttt.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "spectrum-top1.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "xrflines": [
        {
            "visible": true,
            "line_info": {
                "Z": 12,
                "K": true,
                "L": true,
                "M": true,
                "Esc": true
            }
        }
	],
	"panX": 32,
	"zoomY": 3,
	"logScale": false,
	"energyCalibration": [
		{
			"detector": "A",
			"eVStart": 0.1,
			"eVPerChannel": 10.3
		}
	]
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"panY": 10,` + contextImgJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"panY": 11,` + contextImgJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"panY": 12,` + contextImgJSON))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"appliedQuantID": "quant111",
	"quantificationByROI": {
		"roi22": "quant222",
		"roi88": "quant333"
	}
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"roiID": "roi12345",
	"roiName": "The best region",
	"locIdxs": [3,5,7],
	"pixelSelectionImageName": "image.tif",
	"pixelIdxs": [9],
    "cropPixelIdxs": [9]
}`))),
	},
	{ // roi
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"roiColours": {
		"roi99": "rgba(255,255,0,1)",
		"roi22": "rgba(128,0,255,0.5)"
	}
}`))),
	},
	{ // analysisLayout
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"topWidgetSelectors": [
		"context-image",
		"spectrum-widget"
	],
	"bottomWidgetSelectors": [
		"table-widget",
		"binary-plot-widget",
		"rgbu-plot-widget",
		"ternary-plot-widget"
	]
}`))),
	},
	nil, // quant histogram
	nil, // chord-0
	nil, // chord-1
	{ // table
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"showPureElements": true}`))),
	},
	{ // table 2
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"showPureElements": true}`))),
	},
	{ // binary
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{ // ternary
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{ // variogram
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
	},
	{ // rgbuImages
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{ "brightness": 1.2 }`))),
	},
	{ // rgbuPlot
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{ "yChannelA": "B" }`))),
	},
	{ // parallelogram
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{ "colourChannels": ["R", "G"] }`))),
	},
	{ // roiQuantTable
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{ "quantIDs": ["quant1", "quant2"], "roi": "the-roi" }`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "xrflines": [
        {
            "visible": false,
            "line_info": {
                "Z": 12,
                "K": true,
                "L": true,
                "M": true,
                "Esc": true
            }
        }
	],
	"panX": 30,
	"zoomY": 1,
	"logScale": false,
	"energyCalibration": [
		{
			"detector": "A",
			"eVStart": 0.1,
			"eVPerChannel": 10.3
		}
	]
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Various bits should return in the response...
req, _ := http.NewRequest("GET", "/view-state/TheDataSetID", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:


200
{
    "analysisLayout": {
        "topWidgetSelectors": [
            "context-image",
            "spectrum-widget"
        ],
        "bottomWidgetSelectors": [
            "table-widget",
            "binary-plot-widget",
            "rgbu-plot-widget",
            "ternary-plot-widget"
        ]
    },
    "spectrum": {
        "panX": 32,
        "panY": 0,
        "zoomX": 1,
        "zoomY": 3,
        "spectrumLines": [],
        "logScale": false,
        "xrflines": [
            {
                "line_info": {
                    "Z": 12,
                    "K": true,
                    "L": true,
                    "M": true,
                    "Esc": true
                },
                "visible": true
            }
        ],
        "showXAsEnergy": false,
        "energyCalibration": [
            {
                "detector": "A",
                "eVStart": 0.1,
                "eVPerChannel": 10.3
            }
        ]
    },
    "contextImages": {
        "analysis": {
            "panX": 0,
            "panY": 11,
            "zoomX": 2,
            "zoomY": 1,
            "showPoints": true,
            "showPointBBox": false,
            "pointColourScheme": "BW",
            "pointBBoxColourScheme": "PURPLE_CYAN",
            "contextImage": "file1.jpg",
            "contextImageSmoothing": "linear",
            "mapLayers": [
                {
                    "expressionID": "Fe",
                    "opacity": 0.3,
                    "visible": true,
                    "displayValueRangeMin": 12,
                    "displayValueRangeMax": 48.8,
                    "displayValueShading": "SHADE_VIRIDIS"
                }
            ],
            "roiLayers": [
                {
                    "roiID": "roi123",
                    "opacity": 0.7,
                    "visible": true
                }
            ],
            "elementRelativeShading": true,
            "brightness": 1,
            "rgbuChannels": "RGB",
            "unselectedOpacity": 0.4,
            "unselectedGrayscale": false,
            "colourRatioMin": 0,
            "colourRatioMax": 0,
            "removeTopSpecularArtifacts": false,
            "removeBottomSpecularArtifacts": false
        },
        "map": {
            "panX": 0,
            "panY": 10,
            "zoomX": 2,
            "zoomY": 1,
            "showPoints": true,
            "showPointBBox": false,
            "pointColourScheme": "BW",
            "pointBBoxColourScheme": "PURPLE_CYAN",
            "contextImage": "file1.jpg",
            "contextImageSmoothing": "linear",
            "mapLayers": [
                {
                    "expressionID": "Fe",
                    "opacity": 0.3,
                    "visible": true,
                    "displayValueRangeMin": 12,
                    "displayValueRangeMax": 48.8,
                    "displayValueShading": "SHADE_VIRIDIS"
                }
            ],
            "roiLayers": [
                {
                    "roiID": "roi123",
                    "opacity": 0.7,
                    "visible": true
                }
            ],
            "elementRelativeShading": true,
            "brightness": 1,
            "rgbuChannels": "RGB",
            "unselectedOpacity": 0.4,
            "unselectedGrayscale": false,
            "colourRatioMin": 0,
            "colourRatioMax": 0,
            "removeTopSpecularArtifacts": false,
            "removeBottomSpecularArtifacts": false
        }
    },
    "histograms": {},
    "chordDiagrams": {},
    "ternaryPlots": {
        "underspectrum2": {
            "showMmol": false,
            "expressionIDs": [],
            "visibleROIs": []
        }
    },
    "binaryPlots": {
        "underspectrum0": {
            "showMmol": false,
            "expressionIDs": [],
            "visibleROIs": []
        }
    },
    "tables": {
        "undercontext": {
            "showPureElements": true,
            "order": "atomic-number",
            "visibleROIs": []
        }
    },
    "roiQuantTables": {},
    "variograms": {},
    "spectrums": {
        "top1": {
            "panX": 30,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": false,
            "xrflines": [
                {
                    "line_info": {
                        "Z": 12,
                        "K": true,
                        "L": true,
                        "M": true,
                        "Esc": true
                    },
                    "visible": false
                }
            ],
            "showXAsEnergy": false,
            "energyCalibration": [
                {
                    "detector": "A",
                    "eVStart": 0.1,
                    "eVPerChannel": 10.3
                }
            ]
        }
    },
    "rgbuPlots": {
        "underspectrum1": {
            "minerals": [],
            "yChannelA": "B",
            "yChannelB": "",
            "xChannelA": "",
            "xChannelB": "",
            "drawMonochrome": false
        }
    },
    "singleAxisRGBU": {},
    "rgbuImages": {},
    "parallelograms": {},
    "rois": {
        "roiColours": {
            "roi22": "rgba(128,0,255,0.5)",
            "roi99": "rgba(255,255,0,1)"
        }
    },
    "quantification": {
        "appliedQuantID": "quant111"
    },
    "selection": {
        "roiID": "roi12345",
        "roiName": "The best region",
        "locIdxs": [
            3,
            5,
            7
        ],
        "pixelSelectionImageName": "image.tif",
        "pixelIdxs": [
            9
        ],
        "cropPixelIdxs": [
            9
        ]
    }
}
Example (ViewStateHandler_ListCollections)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

viewStateSavedS3Path := viewStateS3Path + "WorkspaceCollections"
sharedViewStateSavedS3Path := sharedViewStateS3Path + "WorkspaceCollections"

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(sharedViewStateSavedS3Path),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateSavedS3Path + "/The view state.json"), LastModified: aws.Time(time.Unix(1634731913, 0))},
			{Key: aws.String(viewStateSavedS3Path + "/Another-state 123.json"), LastModified: aws.Time(time.Unix(1634731914, 0))},
			{Key: aws.String(viewStateSavedS3Path + "/My 10th collection save.json"), LastModified: aws.Time(time.Unix(1634731915, 0))},
		},
	},
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateSavedS3Path + "/For all.json"), LastModified: aws.Time(time.Unix(1634731917, 0))},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Exists, success
req, _ := http.NewRequest("GET", "/view-state/collections/TheDataSetID", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
[
    {
        "name": "The view state",
        "modifiedUnixSec": 1634731913
    },
    {
        "name": "Another-state 123",
        "modifiedUnixSec": 1634731914
    },
    {
        "name": "My 10th collection save",
        "modifiedUnixSec": 1634731915
    },
    {
        "name": "shared-For all",
        "modifiedUnixSec": 1634731917
    }
]
Example (ViewStateHandler_ListSaved)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

viewStateSavedS3Path := viewStateS3Path + "Workspaces"
sharedViewStateSavedS3Path := sharedViewStateS3Path + "Workspaces"

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(sharedViewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(sharedViewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(sharedViewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateSavedS3Path),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(sharedViewStateSavedS3Path),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	nil,
	nil,
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateSavedS3Path + "/viewstate111.json")},
			{Key: aws.String(viewStateSavedS3Path + "/viewstate222.json")},
			{Key: aws.String(viewStateSavedS3Path + "/viewstate333.json")},
		},
	},
	nil,
	nil,
	{
		Contents: []*s3.Object{
			{Key: aws.String(sharedViewStateSavedS3Path + "/forall.json")},
		},
	},
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateSavedS3Path + "/viewstate111.json")},
			{Key: aws.String(viewStateSavedS3Path + "/viewstate222.json")},
			{Key: aws.String(viewStateSavedS3Path + "/viewstate333.json")},
		},
	},
	{
		Contents: []*s3.Object{
			{Key: aws.String(sharedViewStateSavedS3Path + "/forall.json")},
		},
	},
}

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateSavedS3Path + "/viewstate111.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateSavedS3Path + "/viewstate222.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateSavedS3Path + "/viewstate333.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateSavedS3Path + "/forall.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateSavedS3Path + "/viewstate111.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateSavedS3Path + "/viewstate222.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateSavedS3Path + "/viewstate333.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateSavedS3Path + "/forall.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "viewstate111",
    "description": "viewstate111",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "viewstate222",
    "description": "viewstate222",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "viewstate333",
    "description": "viewstate333",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "forall",
    "description": "forall",
    "shared": true,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "viewstate111",
    "description": "viewstate111",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "viewstate222",
    "description": "viewstate222",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "viewstate333",
    "description": "viewstate333",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
    "name": "forall",
    "description": "forall",
    "shared": true,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`))),
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// None
req, _ := http.NewRequest("GET", "/view-state/saved/TheDataSetID", bytes.NewReader([]byte("")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Only user
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Only shared
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Both
req, _ = http.NewRequest("GET", "/view-state/saved/TheDataSetID", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
[]

200
[
    {
        "id": "viewstate111",
        "name": "viewstate111",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    {
        "id": "viewstate222",
        "name": "viewstate222",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    {
        "id": "viewstate333",
        "name": "viewstate333",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
]

200
[
    {
        "id": "shared-forall",
        "name": "forall",
        "shared": true,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
]

200
[
    {
        "id": "viewstate111",
        "name": "viewstate111",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    {
        "id": "viewstate222",
        "name": "viewstate222",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    {
        "id": "viewstate333",
        "name": "viewstate333",
        "shared": false,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    },
    {
        "id": "shared-forall",
        "name": "forall",
        "shared": true,
        "creator": {
            "name": "Niko Bellic",
            "user_id": "600f2a0806b6c70071d3d174",
            "email": "niko@spicule.co.uk"
        }
    }
]
Example (ViewStateHandler_List_WithReset)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Single request results in loading multiple files from S3. First it gets a directory listing...
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateS3Path),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateS3Path + "not-a-widget.json")},                    // Not a recognised file name
			{Key: aws.String(viewStateS3Path + "Workspaces/workspace.json")},            // workspace file, should not be deleted
			{Key: aws.String(viewStateS3Path + "WorkspaceCollections/collection.json")}, // collection file, should not be deleted
			{Key: aws.String(viewStateS3Path + "spectrum.json")},
		},
	},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	// Test 4
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "not-a-widget.json"),
	},
	// Test 5
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "spectrum.json"),
	},
}
mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
	{},
}

// Various bits should return in the response...
req, _ := http.NewRequest("GET", "/view-state/TheDataSetID?reset=true", nil)
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:


200
{
    "analysisLayout": {
        "topWidgetSelectors": [],
        "bottomWidgetSelectors": []
    },
    "spectrum": {
        "panX": 0,
        "panY": 0,
        "zoomX": 1,
        "zoomY": 1,
        "spectrumLines": [],
        "logScale": true,
        "xrflines": [],
        "showXAsEnergy": false,
        "energyCalibration": []
    },
    "contextImages": {},
    "histograms": {},
    "chordDiagrams": {},
    "ternaryPlots": {},
    "binaryPlots": {},
    "tables": {},
    "roiQuantTables": {},
    "variograms": {},
    "spectrums": {},
    "rgbuPlots": {},
    "singleAxisRGBU": {},
    "rgbuImages": {},
    "parallelograms": {},
    "rois": {
        "roiColours": {}
    },
    "quantification": {
        "appliedQuantID": ""
    },
    "selection": {
        "roiID": "",
        "roiName": "",
        "locIdxs": []
    }
}
Example (ViewStateHandler_Post)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// POST not implemented! Should return 405
req, _ := http.NewRequest("POST", "/view-state/TheDataSetID", bytes.NewReader([]byte(`{
	"quantification": "The Name",
	"roi": "12"
}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

405
Example (ViewStateHandler_PutCollection)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/The best collection 23_09_2021.json"), Body: bytes.NewReader([]byte(`{
    "viewStateIDs": [
        "state one",
        "second View State",
        "Third-state"
    ],
    "name": "The best collection 23_09_2021",
    "description": "the desc",
    "viewStates": null,
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`)),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("PUT", "/view-state/collections/TheDataSetID/The best collection 23_09_2021", bytes.NewReader([]byte(`{
    "viewStateIDs": [
        "state one",
        "second View State",
        "Third-state"
    ],
    "name": "The wrong name",
    "description": "the desc"
}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_PutSaved)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate123.json"), Body: bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 0,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {},
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {},
        "binaryPlots": {},
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant111"
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        }
    },
    "name": "viewstate123",
    "description": "",
    "shared": false,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`)),
	},
}

mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

req, _ := http.NewRequest("PUT", "/view-state/saved/TheDataSetID/viewstate123", bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "bottomWidgetSelectors": []
        },
        "rois": {
            "roiColours": {
                "roi22": "rgba(128,0,255,0.5)",
                "roi99": "rgba(255,255,0,1)"
            }
        },
        "quantification": {
            "appliedQuantID": "quant111",
            "quantificationByROI": {
                "roi22": "quant222",
                "roi88": "quant333"
            }
        },
        "selection": {
            "roiID": "roi12345",
            "roiName": "The best region",
            "locIdxs": [
                3,
                5,
                7
            ]
        }
    },
    "name": "viewstate123 INCORRECT VIEW STATE SHOULD BE REPLACED!"
}`)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_all)

Saving an entire view state. This clears view state files in the S3 directory we're writing to, and writes new ones based on the view state being passed in

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// Expecting a listing of view state dir
mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(viewStateS3Path),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	{
		Contents: []*s3.Object{
			{Key: aws.String(viewStateS3Path + "not-a-widget.json")},                    // Not a recognised file name
			{Key: aws.String(viewStateS3Path + "Workspaces/workspace.json")},            // workspace file, should not be deleted
			{Key: aws.String(viewStateS3Path + "WorkspaceCollections/collection.json")}, // collection file, should not be deleted
			{Key: aws.String(viewStateS3Path + "spectrum-top1.json")},
		},
	},
}

// Expecting to delete only view state files (not workspace/collection)
mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	// Test 4
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "not-a-widget.json"),
	},
	// Test 5
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "spectrum-top1.json"),
	},
}
mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
	{},
}

// Expecting a PUT for layout, selection, quant, ROI and each widget
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "roi.json"), Body: bytes.NewReader([]byte(`{
    "roiColours": {
        "roi22": "rgba(128,0,255,0.5)",
        "roi33": "rgba(255,255,0,1)"
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "quantification.json"), Body: bytes.NewReader([]byte(`{
    "appliedQuantID": "9qntb8w2joq4elti"
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "selection.json"), Body: bytes.NewReader([]byte(`{
    "roiID": "",
    "roiName": "",
    "locIdxs": [
        345,
        347,
        348,
        1273
    ]
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "analysisLayout.json"), Body: bytes.NewReader([]byte(`{
    "topWidgetSelectors": [
        "context-image",
        "spectrum-widget"
    ],
    "bottomWidgetSelectors": [
        "table-widget",
        "binary-plot-widget",
        "rgbu-plot-widget",
        "ternary-plot-widget"
    ]
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "contextImage-analysis.json"), Body: bytes.NewReader([]byte(`{
    "panX": -636.63446,
    "panY": -674.23505,
    "zoomX": 2.6251905,
    "zoomY": 2.6251905,
    "showPoints": true,
    "showPointBBox": true,
    "pointColourScheme": "PURPLE_CYAN",
    "pointBBoxColourScheme": "PURPLE_CYAN",
    "contextImage": "PCCR0257_0689789827_000MSA_N008000008906394300060LUD01.tif",
    "contextImageSmoothing": "linear",
    "mapLayers": [],
    "roiLayers": [
        {
            "roiID": "AllPoints",
            "opacity": 1,
            "visible": false
        },
        {
            "roiID": "SelectedPoints",
            "opacity": 1,
            "visible": false
        }
    ],
    "elementRelativeShading": true,
    "brightness": 1,
    "rgbuChannels": "R/G",
    "unselectedOpacity": 0.3,
    "unselectedGrayscale": false,
    "colourRatioMin": 0.5,
    "colourRatioMax": 2.25,
    "removeTopSpecularArtifacts": false,
    "removeBottomSpecularArtifacts": false
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "contextImage-map.json"), Body: bytes.NewReader([]byte(`{
    "panX": -116.896935,
    "panY": -145.20177,
    "zoomX": 1.0904286,
    "zoomY": 1.0904286,
    "showPoints": true,
    "showPointBBox": true,
    "pointColourScheme": "PURPLE_CYAN",
    "pointBBoxColourScheme": "PURPLE_CYAN",
    "contextImage": "",
    "contextImageSmoothing": "linear",
    "mapLayers": [],
    "roiLayers": [
        {
            "roiID": "AllPoints",
            "opacity": 1,
            "visible": false
        },
        {
            "roiID": "SelectedPoints",
            "opacity": 1,
            "visible": false
        }
    ],
    "elementRelativeShading": true,
    "brightness": 1,
    "rgbuChannels": "RGB",
    "unselectedOpacity": 0.4,
    "unselectedGrayscale": false,
    "colourRatioMin": 0,
    "colourRatioMax": 0,
    "removeTopSpecularArtifacts": false,
    "removeBottomSpecularArtifacts": false
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "ternary-underspectrum2.json"), Body: bytes.NewReader([]byte(`{
    "showMmol": false,
    "expressionIDs": [
        "vge9tz6fkbi2ha1p",
        "shared-j1g1sx285s6yqjih",
        "r4zd5s2tfgr8rahy"
    ],
    "visibleROIs": [
        "AllPoints",
        "SelectedPoints"
    ]
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "rgbuPlot-underspectrum1.json"), Body: bytes.NewReader([]byte(`{
    "minerals": [],
    "yChannelA": "B",
    "yChannelB": "U",
    "xChannelA": "R",
    "xChannelB": "B",
    "drawMonochrome": false
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "spectrum-top1.json"), Body: bytes.NewReader([]byte(`{
    "panX": -53.19157,
    "panY": -37.737877,
    "zoomX": 3.5776386,
    "zoomY": 1.3382256,
    "spectrumLines": [
        {
            "roiID": "AllPoints",
            "lineExpressions": [
                "bulk(A)",
                "bulk(B)"
            ]
        },
        {
            "roiID": "SelectedPoints",
            "lineExpressions": [
                "bulk(A)",
                "bulk(B)"
            ]
        }
    ],
    "logScale": true,
    "xrflines": [],
    "showXAsEnergy": true,
    "energyCalibration": [
        {
            "detector": "A",
            "eVStart": -20.759016,
            "eVPerChannel": 7.8629937
        },
        {
            "detector": "B",
            "eVStart": -20.759016,
            "eVPerChannel": 7.8629937
        }
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
	{},
	{},
	{},
	{},
	{},
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const wholeState = `{
	"analysisLayout": {
		"topWidgetSelectors": ["context-image", "spectrum-widget"],
		"bottomWidgetSelectors": ["table-widget", "binary-plot-widget", "rgbu-plot-widget", "ternary-plot-widget"]
	},
	"contextImages": {
		"analysis": {
			"panX": -636.63446,
			"panY": -674.23505,
			"zoomX": 2.6251905,
			"zoomY": 2.6251905,
			"showPoints": true,
			"showPointBBox": true,
			"pointColourScheme": "PURPLE_CYAN",
			"pointBBoxColourScheme": "PURPLE_CYAN",
			"contextImage": "PCCR0257_0689789827_000MSA_N008000008906394300060LUD01.tif",
			"contextImageSmoothing": "linear",
			"mapLayers": [],
			"roiLayers": [{
				"roiID": "AllPoints",
				"opacity": 1,
				"visible": false
			}, {
				"roiID": "SelectedPoints",
				"opacity": 1,
				"visible": false
			}],
			"elementRelativeShading": true,
			"brightness": 1,
			"rgbuChannels": "R/G",
			"unselectedOpacity": 0.3,
			"unselectedGrayscale": false,
			"colourRatioMin": 0.5,
			"colourRatioMax": 2.25,
			"removeTopSpecularArtifacts": false,
			"removeBottomSpecularArtifacts": false
		},
		"map": {
			"panX": -116.896935,
			"panY": -145.20177,
			"zoomX": 1.0904286,
			"zoomY": 1.0904286,
			"showPoints": true,
			"showPointBBox": true,
			"pointColourScheme": "PURPLE_CYAN",
			"pointBBoxColourScheme": "PURPLE_CYAN",
			"contextImage": "",
			"contextImageSmoothing": "linear",
			"mapLayers": [],
			"roiLayers": [{
				"roiID": "AllPoints",
				"opacity": 1,
				"visible": false
			}, {
				"roiID": "SelectedPoints",
				"opacity": 1,
				"visible": false
			}],
			"elementRelativeShading": true,
			"brightness": 1,
			"rgbuChannels": "RGB",
			"unselectedOpacity": 0.4,
			"unselectedGrayscale": false,
			"colourRatioMin": 0,
			"colourRatioMax": 0,
			"removeTopSpecularArtifacts": false,
			"removeBottomSpecularArtifacts": false
		}
	},
	"histograms": {},
	"chordDiagrams": {},
	"ternaryPlots": {
		"undercontext": {
			"showMmol": false,
			"expressionIDs": ["expr-elem-K2O-%(Combined)", "expr-elem-Na2O-%(Combined)", "expr-elem-MgO-%(Combined)"],
			"visibleROIs": ["AllPoints", "9s5vkwjxl6539jbp", "tsiaam7uvs00yjom", "und4hnr30l61ha3u", "newa1c3apifnygtm", "y0o44g8n4z3ts40x", "SelectedPoints"]
		},
		"underspectrum1": {
			"showMmol": false,
			"expressionIDs": ["expr-elem-Na2O-%(Combined)", "shared-uds1s1t27qf97b03", "expr-elem-MgO-%(Combined)"],
			"visibleROIs": ["AllPoints", "SelectedPoints"]
		},
		"underspectrum2": {
			"showMmol": false,
			"expressionIDs": ["vge9tz6fkbi2ha1p", "shared-j1g1sx285s6yqjih", "r4zd5s2tfgr8rahy"],
			"visibleROIs": ["AllPoints", "SelectedPoints"]
		}
	},
	"binaryPlots": {
		"undercontext": {
			"showMmol": false,
			"expressionIDs": ["", ""],
			"visibleROIs": ["AllPoints", "SelectedPoints"]
		},
		"underspectrum1": {
			"showMmol": false,
			"expressionIDs": ["expr-elem-SiO2-%(Combined)", "expr-elem-Al2O3-%(Combined)"],
			"visibleROIs": ["AllPoints", "SelectedPoints"]
		}
	},
	"tables": {
		"underspectrum0": {
			"showPureElements": false,
			"order": "atomic-number",
			"visibleROIs": ["AllPoints", "SelectedPoints", "jvi1p1awm77fsywc", "6mbhyd8nbyj4um4p", "1mk9xra5qejh3tvk"]
		}
	},
	"roiQuantTables": {},
	"variograms": {
		"undercontext": {
			"expressionIDs": ["expr-elem-K2O-%"],
			"visibleROIs": ["AllPoints", "SelectedPoints"],
			"varioModel": "exponential",
			"maxDistance": 6.5188847,
			"binCount": 1668,
			"drawModeVector": false
		}
	},
	"spectrums": {
		"top0": {
			"panX": -137.13159,
			"panY": 0,
			"zoomX": 1.6592865,
			"zoomY": 1,
			"spectrumLines": [{
				"roiID": "AllPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}, {
				"roiID": "SelectedPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}],
			"logScale": true,
			"xrflines": [],
			"showXAsEnergy": true,
			"energyCalibration": [{
				"detector": "A",
				"eVStart": -18.5,
				"eVPerChannel": 7.862
			}, {
				"detector": "B",
				"eVStart": -22.4,
				"eVPerChannel": 7.881
			}]
		},
		"top1": {
			"panX": -53.19157,
			"panY": -37.737877,
			"zoomX": 3.5776386,
			"zoomY": 1.3382256,
			"spectrumLines": [{
				"roiID": "AllPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}, {
				"roiID": "SelectedPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}],
			"logScale": true,
			"xrflines": [],
			"showXAsEnergy": true,
			"energyCalibration": [{
				"detector": "A",
				"eVStart": -20.759016,
				"eVPerChannel": 7.8629937
			}, {
				"detector": "B",
				"eVStart": -20.759016,
				"eVPerChannel": 7.8629937
			}]
		},
		"undercontext": {
			"panX": 0,
			"panY": 0,
			"zoomX": 1,
			"zoomY": 1,
			"spectrumLines": [{
				"roiID": "AllPoints",
				"lineExpressions": ["bulk(A)"]
			}],
			"logScale": true,
			"xrflines": [],
			"showXAsEnergy": true,
			"energyCalibration": [{
				"detector": "A",
				"eVStart": -18.5,
				"eVPerChannel": 7.862
			}, {
				"detector": "B",
				"eVStart": -22.4,
				"eVPerChannel": 7.881
			}]
		},
		"underspectrum0": {
			"panX": 0,
			"panY": 0,
			"zoomX": 1,
			"zoomY": 1,
			"spectrumLines": [{
				"roiID": "AllPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}, {
				"roiID": "SelectedPoints",
				"lineExpressions": ["bulk(A)"]
			}],
			"logScale": true,
			"xrflines": [],
			"showXAsEnergy": true,
			"energyCalibration": [{
				"detector": "A",
				"eVStart": -18.5,
				"eVPerChannel": 7.862
			}, {
				"detector": "B",
				"eVStart": -22.4,
				"eVPerChannel": 7.881
			}]
		},
		"underspectrum1": {
			"panX": 0,
			"panY": 0,
			"zoomX": 1,
			"zoomY": 1,
			"spectrumLines": [{
				"roiID": "AllPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}, {
				"roiID": "SelectedPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}],
			"logScale": true,
			"xrflines": [],
			"showXAsEnergy": true,
			"energyCalibration": [{
				"detector": "A",
				"eVStart": -18.5,
				"eVPerChannel": 7.862
			}, {
				"detector": "B",
				"eVStart": -22.4,
				"eVPerChannel": 7.881
			}]
		},
		"underspectrum2": {
			"panX": 0,
			"panY": 0,
			"zoomX": 1,
			"zoomY": 1,
			"spectrumLines": [{
				"roiID": "SelectedPoints",
				"lineExpressions": ["bulk(A)", "bulk(B)"]
			}],
			"logScale": true,
			"xrflines": [],
			"showXAsEnergy": true,
			"energyCalibration": [{
				"detector": "A",
				"eVStart": -18.5,
				"eVPerChannel": 7.862
			}, {
				"detector": "B",
				"eVStart": -22.4,
				"eVPerChannel": 7.881
			}]
		}
	},
	"rgbuPlots": {
		"underspectrum0": {
			"minerals": ["plag", "sanidine", "microline", "aug", "opx", "Fo89", "Fo11", "Chalcedor", "calsite", "gypsum", "dolomite", "FeS2", "FeS", "Fe3O4"],
			"yChannelA": "G",
			"yChannelB": "R",
			"xChannelA": "B",
			"xChannelB": "R",
			"drawMonochrome": false
		},
		"underspectrum1": {
			"minerals": [],
			"yChannelA": "B",
			"yChannelB": "U",
			"xChannelA": "R",
			"xChannelB": "B",
			"drawMonochrome": false
		},
		"underspectrum2": {
			"minerals": [],
			"yChannelA": "U",
			"yChannelB": "R",
			"xChannelA": "U",
			"xChannelB": "B",
			"drawMonochrome": false
		}
	},
	"singleAxisRGBU": {},
	"rgbuImages": {
		"top1": {
			"logColour": false,
			"brightness": 1
		}
	},
	"parallelograms": {},
	"rois": {
		"roiColours": {
			"roi22": "rgba(128,0,255,0.5)",
			"roi33": "rgba(255,255,0,1)"
		}
	},
	"quantification": {
		"appliedQuantID": "9qntb8w2joq4elti"
	},
	"selection": {
		"roiID": "",
		"roiName": "",
		"locIdxs": [345, 347, 348, 1273]
	}
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"all", bytes.NewReader([]byte(wholeState)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_analysisLayout)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "analysisLayout.json"), Body: bytes.NewReader([]byte(`{
    "topWidgetSelectors": [
        "spectrum"
    ],
    "bottomWidgetSelectors": [
        "chord",
        "binary"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "topWidgetSelectors": [
        "spectrum"
    ],
    "bottomWidgetSelectors": [
        "chord",
        "binary"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"analysisLayout", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_binary)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "binary-bottom12.json"), Body: bytes.NewReader([]byte(`{
    "showMmol": false,
    "expressionIDs": [
        "Fe",
        "Ca"
    ],
    "visibleROIs": [
        "roi123",
        "roi456"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "showMmol": false,
    "visibleROIs": [
        "roi123",
        "roi456"
    ],
    "expressionIDs": [
        "Fe",
        "Ca"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"binary-bottom12", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_chord)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "chord-111.json"), Body: bytes.NewReader([]byte(`{
    "showForSelection": false,
    "expressionIDs": [
        "abc123"
    ],
    "displayROI": "roi999",
    "threshold": 0.8,
    "drawMode": "POSITIVE"
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "showForSelection": false,
    "expressionIDs": [
        "abc123"
	],
	"displayROI": "roi999",
    "threshold": 0.8,
    "drawMode": "POSITIVE"
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"chord-111", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_contextImage)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "contextImage-analysis.json"), Body: bytes.NewReader([]byte(`{
    "panX": 12,
    "panY": 0,
    "zoomX": 1,
    "zoomY": 0,
    "showPoints": true,
    "showPointBBox": false,
    "pointColourScheme": "BW",
    "pointBBoxColourScheme": "PURPLE_CYAN",
    "contextImage": "context123.png",
    "contextImageSmoothing": "nearest",
    "mapLayers": [
        {
            "expressionID": "Ca",
            "opacity": 0.1,
            "visible": false,
            "displayValueRangeMin": 12,
            "displayValueRangeMax": 48.8,
            "displayValueShading": "SHADE_VIRIDIS"
        },
        {
            "expressionID": "Ti",
            "opacity": 0.4,
            "visible": true,
            "displayValueRangeMin": 24,
            "displayValueRangeMax": 25.5,
            "displayValueShading": "SHADE_PURPLE"
        }
    ],
    "roiLayers": [
        {
            "roiID": "roi111",
            "opacity": 0.8,
            "visible": true
        }
    ],
    "elementRelativeShading": false,
    "brightness": 1.3,
    "rgbuChannels": "GRU",
    "unselectedOpacity": 0.2,
    "unselectedGrayscale": true,
    "colourRatioMin": 0,
    "colourRatioMax": 1.3,
    "removeTopSpecularArtifacts": false,
    "removeBottomSpecularArtifacts": false
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "panX": 12,
    "zoomX": 1,
    "showPoints": true,
    "showPointBBox": false,
    "pointColourScheme": "BW",
    "pointBBoxColourScheme": "PURPLE_CYAN",
    "contextImage": "context123.png",
    "contextImageSmoothing": "nearest",
    "brightness": 1.3,
    "rgbuChannels": "GRU",
    "unselectedOpacity": 0.2,
    "unselectedGrayscale": true,
    "colourRatioMax": 1.3,
    "mapLayers": [
        {
            "expressionID": "Ca",
            "opacity": 0.1,
            "visible": false,
            "displayValueRangeMin": 12,
            "displayValueRangeMax": 48.8,
            "displayValueShading": "SHADE_VIRIDIS"
        },
        {
            "expressionID": "Ti",
            "opacity": 0.4,
            "visible": true,
            "displayValueRangeMin": 24,
            "displayValueRangeMax": 25.5,
            "displayValueShading": "SHADE_PURPLE"
        }
    ],
    "roiLayers": [
        {
            "roiID": "roi111",
            "opacity": 0.8,
            "visible": true
        }
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"contextImage-analysis", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_histogram)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "histogram-top-left.json"), Body: bytes.NewReader([]byte(`{
    "showStdDeviation": true,
    "logScale": true,
    "expressionIDs": [
        "Fe",
        "Ca"
    ],
    "visibleROIs": [
        "roi123",
        "roi456",
        "roi789"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "showStdDeviation": true,
    "logScale": true,
    "expressionIDs": [
        "Fe",
        "Ca"
    ],
    "visibleROIs": [
        "roi123",
        "roi456",
        "roi789"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"histogram-top-left", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_parallelogram)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "parallelogram-5.json"), Body: bytes.NewReader([]byte(`{
    "colourChannels": [
        "R",
        "G",
        "B"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "colourChannels": [
        "R",
        "G",
        "B"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"parallelogram-5", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_quantification)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "quantification.json"), Body: bytes.NewReader([]byte(`{
    "appliedQuantID": "54321"
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
	"appliedQuantID": "54321"
}`

const routePath = "/view-state/TheDataSetID/"

// Spectrum
req, _ := http.NewRequest("PUT", routePath+"quantification", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_rgbuImages)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "rgbuImages-5.json"), Body: bytes.NewReader([]byte(`{
    "logColour": false,
    "brightness": 1.2
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "logColour": false,
    "brightness": 1.2
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"rgbuImages-5", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_rgbuPlots)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "rgbuPlot-5.json"), Body: bytes.NewReader([]byte(`{
    "minerals": [
        "Plagioclase",
        "Olivine"
    ],
    "yChannelA": "B",
    "yChannelB": "U",
    "xChannelA": "R",
    "xChannelB": "G",
    "drawMonochrome": true
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "xChannelA": "R",
	"xChannelB": "G",
	"yChannelA": "B",
	"yChannelB": "U",
	"drawMonochrome": true,
    "minerals": [
        "Plagioclase",
        "Olivine"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"rgbuPlot-5", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_roi)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "roi.json"), Body: bytes.NewReader([]byte(`{
    "roiColours": {
        "roi22": "rgba(128,0,255,0.5)",
        "roi33": "rgba(255,255,0,1)"
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
	"roiColours": {
		"roi33": "rgba(255,255,0,1)",
		"roi22": "rgba(128,0,255,0.5)"
	}
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"roi", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_roiQuantTable)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "roiQuantTable-5.json"), Body: bytes.NewReader([]byte(`{
    "roi": "something",
    "quantIDs": [
        "quant1",
        "Q2"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "roi": "something",
    "quantIDs": [
        "quant1",
        "Q2"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"roiQuantTable-5", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_selection)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "selection.json"), Body: bytes.NewReader([]byte(`{
    "roiID": "3333",
    "roiName": "Dark patch",
    "locIdxs": [
        999,
        888,
        777
    ],
    "pixelSelectionImageName": "file.tif",
    "pixelIdxs": [
        333
    ],
    "cropPixelIdxs": [
        333,
        334
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "roiID": "3333",
    "roiName": "Dark patch",
    "locIdxs": [
        999,
        888,
        777
    ],
    "pixelSelectionImageName": "file.tif",
    "pixelIdxs": [
        333
    ],
    "cropPixelIdxs": [
        333,
		334
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"selection", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_spectrum_oldway_FAIL)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "panX": 12,
    "zoomX": 1,
    "energyCalibration": [
        {
            "detector": "B",
            "eVStart": 12.5,
            "eVPerChannel": 17.8
        }
    ],
    "logScale": true,
    "spectrumLines": [
        {
            "roiID": "dataset",
            "lineExpressions": [
                "bulk(A)",
                "bulk(B)"
            ]
        },
        {
            "roiID": "selection",
            "lineExpressions": [
                "sum(bulk(A), bulk(B))"
            ]
        },
        {
            "roiID": "roi-123",
            "lineExpressions": [
                "sum(bulk(A), bulk(B))"
            ]
        }
    ],
    "xrflines": [
        {
            "visible": true,
            "line_info": {
                "Z": 12,
                "K": true,
                "L": true,
                "M": true,
                "Esc": true
            }
        }
    ],
    "showXAsEnergy": true
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"spectrum", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
Unknown widget: spectrum
Example (ViewStateHandler_Put_spectrum_topright)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("/UserContent/notifications/myuserid.json"),
	},
}
mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"userid":"myuserid","notifications":{"topics":[],"hints":["point-select-alt","point-select-z-for-zoom","point-select-shift-for-pan","lasso-z-for-zoom","lasso-shift-for-pan","dwell-exists-test-fm-5x5-full","dwell-exists-069927431"],"uinotifications":[]},"userconfig":{"name":"peternemere","email":"peternemere@gmail.com","cell":"","data_collection":"1.0"}}`)))},
}

testServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer testServer.Close()
//"Component":"http://example.com/foo","Message":"{\"alive\": true}","Version":"","Params":{"method":"GET"},"Environment":"unit-test","User":"myuserid"}
var ExpIndexObject = []string{
	`{"Instance":"","Time":"0000-00-00T00:00:00-00:00","Component":"/view-state/TheDataSetID/spectrum-top1","Message":"{\n    \"panX\": 12,\n    \"zoomX\": 1,\n    \"energyCalibration\": [\n        {\n            \"detector\": \"B\",\n            \"eVStart\": 12.5,\n            \"eVPerChannel\": 17.8\n        }\n    ],\n    \"logScale\": true,\n    \"spectrumLines\": [\n        {\n            \"roiID\": \"dataset\",\n            \"lineExpressions\": [\n                \"bulk(A)\",\n                \"bulk(B)\"\n            ]\n        },\n        {\n            \"roiID\": \"selection\",\n            \"lineExpressions\": [\n                \"sum(bulk(A), bulk(B))\"\n            ]\n        },\n        {\n            \"roiID\": \"roi-123\",\n            \"lineExpressions\": [\n                \"sum(bulk(A), bulk(B))\"\n            ]\n        }\n    ],\n    \"xrflines\": [\n        {\n            \"visible\": true,\n            \"line_info\": {\n                \"Z\": 12,\n                \"K\": true,\n                \"L\": true,\n                \"M\": true,\n                \"Esc\": true\n            }\n        }\n    ],\n    \"showXAsEnergy\": true\n}","Response":"","Version":"","Params":{"method":"PUT"},"Environment":"unit-test","User":"myuserid"}`,
}
var ExpRespObject = []string{
	`{"_index":"metrics","_type":"trigger","_id":"B0tzT3wBosV6bFs8gJvY","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":8468,"_primary_term":1}`,
}

var adjtime = "0000-00-00T00:00:00-00:00"
d := esutil.DummyElasticClient{}
foo, err := d.DummyElasticSearchClient(testServer.URL, ExpRespObject, ExpIndexObject, ExpRespObject, &adjtime)
//defer d.FinishTest()
if err != nil {
	fmt.Printf("%v\n", err)
}

apiConfig := config.APIConfig{EnvironmentName: "Test"}
connection, err := esutil.Connect(foo, apiConfig)
// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "spectrum-top1.json"), Body: bytes.NewReader([]byte(`{
    "panX": 12,
    "panY": 0,
    "zoomX": 1,
    "zoomY": 0,
    "spectrumLines": [
        {
            "roiID": "dataset",
            "lineExpressions": [
                "bulk(A)",
                "bulk(B)"
            ]
        },
        {
            "roiID": "selection",
            "lineExpressions": [
                "sum(bulk(A), bulk(B))"
            ]
        },
        {
            "roiID": "roi-123",
            "lineExpressions": [
                "sum(bulk(A), bulk(B))"
            ]
        }
    ],
    "logScale": true,
    "xrflines": [
        {
            "line_info": {
                "Z": 12,
                "K": true,
                "L": true,
                "M": true,
                "Esc": true
            },
            "visible": true
        }
    ],
    "showXAsEnergy": true,
    "energyCalibration": [
        {
            "detector": "B",
            "eVStart": 12.5,
            "eVPerChannel": 17.8
        }
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, &connection, nil)
apiRouter := MakeRouter(svcs)
mockvalidator := api.MockJWTValidator{}
logware := LoggerMiddleware{&svcs, &mockvalidator}

apiRouter.Router.Use(logware.Middleware)
const putItem = `{
    "panX": 12,
    "zoomX": 1,
    "energyCalibration": [
        {
            "detector": "B",
            "eVStart": 12.5,
            "eVPerChannel": 17.8
        }
    ],
    "logScale": true,
    "spectrumLines": [
        {
            "roiID": "dataset",
            "lineExpressions": [
                "bulk(A)",
                "bulk(B)"
            ]
        },
        {
            "roiID": "selection",
            "lineExpressions": [
                "sum(bulk(A), bulk(B))"
            ]
        },
        {
            "roiID": "roi-123",
            "lineExpressions": [
                "sum(bulk(A), bulk(B))"
            ]
        }
    ],
    "xrflines": [
        {
            "visible": true,
            "line_info": {
                "Z": 12,
                "K": true,
                "L": true,
                "M": true,
                "Esc": true
            }
        }
    ],
    "showXAsEnergy": true
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"spectrum-top1", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

&map[]200
Example (ViewStateHandler_Put_table)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "table-5.json"), Body: bytes.NewReader([]byte(`{
    "showPureElements": false,
    "order": "something",
    "visibleROIs": [
        "roi123",
        "roi456"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "showPureElements": false,
    "order": "something",
    "visibleROIs": [
        "roi123",
        "roi456"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"table-5", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_Put_ternary)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "ternary-5.json"), Body: bytes.NewReader([]byte(`{
    "showMmol": false,
    "expressionIDs": [
        "Fe",
        "Ca",
        "Sr"
    ],
    "visibleROIs": [
        "roi123",
        "roi456"
    ]
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

const putItem = `{
    "showMmol": false,
    "visibleROIs": [
        "roi123",
        "roi456"
    ],
    "expressionIDs": [
        "Fe",
        "Ca",
        "Sr"
    ]
}`

const routePath = "/view-state/TheDataSetID/"

req, _ := http.NewRequest("PUT", routePath+"ternary-5", bytes.NewReader([]byte(putItem)))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
Example (ViewStateHandler_RenamingWorkspaces)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()

collectionRoot := "UserContent/600f2a0806b6c70071d3d174/TheDataSetID/ViewState/WorkspaceCollections"

mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 3
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate123.json"),
	},

	// Test 4
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},

	// Test 5: the view state
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate999.json"),
	},
	// And the collections
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(collectionRoot + "/a collection.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(collectionRoot + "/Another-Collection.json"),
	},
}

mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	// Test 3: no view state file
	nil,
	// Test 4: exists
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"spectrum": {"panX": 993}}}`))),
	},
	// Test 5: exists
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"spectrum": {"panX": 993}}}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "a collection", "viewStateIDs": ["some view state", "viewstate999", "third one"]}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"name": "Another-Collection", "viewStateIDs": ["also not the one"]}`))),
	},
}

savedRenamedViewState := `{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 993,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {},
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {},
        "binaryPlots": {},
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {}
        },
        "quantification": {
            "appliedQuantID": ""
        },
        "selection": {
            "roiID": "",
            "roiName": "",
            "locIdxs": []
        }
    },
    "name": "",
    "description": ""
}`

mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	// Test 4: saving new workspace
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/new-ID_here.json"), Body: bytes.NewReader([]byte(savedRenamedViewState)),
	},
	// Test 5: upload collection first
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/a collection.json"), Body: bytes.NewReader([]byte(`{
    "viewStateIDs": [
        "some view state",
        "new-ID_here",
        "third one"
    ],
    "name": "a collection",
    "description": "",
    "viewStates": null
}`)),
	},
	// saving new workspace
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/new-ID_here.json"), Body: bytes.NewReader([]byte(savedRenamedViewState)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
}

mockS3.ExpDeleteObjectInput = []s3.DeleteObjectInput{
	// Test 4
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate555.json"),
	},
	// Test 5
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/viewstate999.json"),
	},
}
mockS3.QueuedDeleteObjectOutput = []*s3.DeleteObjectOutput{
	{},
	{},
}

mockS3.ExpListObjectsV2Input = []s3.ListObjectsV2Input{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(collectionRoot),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Prefix: aws.String(collectionRoot),
	},
}
mockS3.QueuedListObjectsV2Output = []*s3.ListObjectsV2Output{
	// Test 4: no collections
	{
		Contents: []*s3.Object{},
	},
	// Test 5: 2 collections
	{
		Contents: []*s3.Object{
			{Key: aws.String(collectionRoot + "/a collection.json"), LastModified: aws.Time(time.Unix(1634731910, 0))},
			{Key: aws.String(collectionRoot + "/Another-Collection.json"), LastModified: aws.Time(time.Unix(1634731916, 0))},
		},
	},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// Same name, should fail
req, _ := http.NewRequest("POST", "/view-state/saved/TheDataSetID/viewstate123/rename", bytes.NewReader([]byte("viewstate123")))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Empty name, should fail
req, _ = http.NewRequest("POST", "/view-state/saved/TheDataSetID/viewstate456/rename", bytes.NewReader([]byte("")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Doesn't exist, should fail
req, _ = http.NewRequest("POST", "/view-state/saved/TheDataSetID/viewstate123/rename", bytes.NewReader([]byte("new-ID/here!")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, no collections, success
req, _ = http.NewRequest("POST", "/view-state/saved/TheDataSetID/viewstate555/rename", bytes.NewReader([]byte("new-ID/here!")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Exists, renamed in 1 collection, success
req, _ = http.NewRequest("POST", "/view-state/saved/TheDataSetID/viewstate999/rename", bytes.NewReader([]byte("new-ID/here!")))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

400
New workspace name must be different to previous name

400
New workspace name must be different to previous name

404
viewstate123 not found

200

200
Example (ViewStateHandler_ShareCollection)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 1 - just collection
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/331.json"),
	},
	// Test 2 - just collection
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/332.json"),
	},
	// Test 3 - collection+view state, which fails
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/333.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/The first one.json"),
	},
	// Test 4 - collection+view state files
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "WorkspaceCollections/334.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/The first one.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/Another workspace.json"),
	},
}

const collectionResp = `{
    "name": "Another_collection-01-01-2022",
    "viewStateIDs": [
        "The first one",
        "Another workspace"
    ],
	"description": "some description"
}`

mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(collectionResp))),
	},
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(collectionResp))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"quantification": {"appliedQuantID": "quant1"}}}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"viewState": {"quantification": {"appliedQuantID": "quant2"}}}`))),
	},
}

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "WorkspaceCollections/334.json"), Body: bytes.NewReader([]byte(`{
    "viewStateIDs": [
        "The first one",
        "Another workspace"
    ],
    "name": "Another_collection-01-01-2022",
    "description": "some description",
    "viewStates": {
        "Another workspace": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant2"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        },
        "The first one": {
            "analysisLayout": {
                "topWidgetSelectors": [],
                "bottomWidgetSelectors": []
            },
            "spectrum": {
                "panX": 0,
                "panY": 0,
                "zoomX": 1,
                "zoomY": 1,
                "spectrumLines": [],
                "logScale": true,
                "xrflines": [],
                "showXAsEnergy": false,
                "energyCalibration": []
            },
            "contextImages": {},
            "histograms": {},
            "chordDiagrams": {},
            "ternaryPlots": {},
            "binaryPlots": {},
            "tables": {},
            "roiQuantTables": {},
            "variograms": {},
            "spectrums": {},
            "rgbuPlots": {},
            "singleAxisRGBU": {},
            "rgbuImages": {},
            "parallelograms": {},
            "rois": {
                "roiColours": {}
            },
            "quantification": {
                "appliedQuantID": "quant1"
            },
            "selection": {
                "roiID": "",
                "roiName": "",
                "locIdxs": []
            }
        }
    },
    "shared": true,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/view-state-collection/TheDataSetID/331", bytes.NewReader([]byte{}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("POST", "/share/view-state-collection/TheDataSetID/332", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Referenced view state file not found
req, _ = http.NewRequest("POST", "/share/view-state-collection/TheDataSetID/333", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File and view states found, share OK
req, _ = http.NewRequest("POST", "/share/view-state-collection/TheDataSetID/334", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
331 not found

404
332 not found

404
The first one not found

200
"334 shared"
Example (ViewStateHandler_ShareViewState)
var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 1
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/331.json"),
	},
	// Test 2
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/332.json"),
	},
	// Test 3
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/333.json"),
	},
	// Test 4
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/334.json"),
	},
}

mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	nil,
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`}`))),
	},
	{
		// View state that references non-shared IDs. We want to make sure it returns the right ones and
		// count, so we return multiple IDs here:
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				 "viewState": {
					"quantification": {"appliedQuantID": "quant123"},
					"binaryPlots": { "44": { "expressionIDs": ["shared-expr", "expr1"], "visibleROIs": ["shared-roi"] } },
					"ternaryPlots": { "66": { "expressionIDs": ["shared-expr2"], "visibleROIs": ["roi2"] } }
				 },
				 "name": "333",
				"description": "the description of 333"
			}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				"viewState": {
					"quantification": {"appliedQuantID": "shared-quant123"},
					"binaryPlots": { "77": { "expressionIDs": ["shared-expr", "shared-expr1"], "visibleROIs": ["shared-roi"] } },
					"ternaryPlots": { "99": { "expressionIDs": ["shared-expr2"], "visibleROIs": ["shared-roi2"] } }
				},
				 "name": "334",
				"description": "the description of 334"
			}`))),
	},
}

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(sharedViewStateS3Path + "Workspaces/334.json"), Body: bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 0,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {},
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {
            "99": {
                "showMmol": false,
                "expressionIDs": [
                    "shared-expr2"
                ],
                "visibleROIs": [
                    "shared-roi2"
                ]
            }
        },
        "binaryPlots": {
            "77": {
                "showMmol": false,
                "expressionIDs": [
                    "shared-expr",
                    "shared-expr1"
                ],
                "visibleROIs": [
                    "shared-roi"
                ]
            }
        },
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {}
        },
        "quantification": {
            "appliedQuantID": "shared-quant123"
        },
        "selection": {
            "roiID": "",
            "roiName": "",
            "locIdxs": []
        }
    },
    "name": "334",
    "description": "the description of 334",
    "shared": true,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
}

svcs := MakeMockSvcs(&mockS3, nil, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/view-state/TheDataSetID/331", bytes.NewReader([]byte{}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// File empty in S3, should say not found
req, _ = http.NewRequest("POST", "/share/view-state/TheDataSetID/332", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Share failed because of non-shared ids referenced by workspace
req, _ = http.NewRequest("POST", "/share/view-state/TheDataSetID/333", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Share OK
req, _ = http.NewRequest("POST", "/share/view-state/TheDataSetID/334", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)

// Sharing a shared one - should fail
req, _ = http.NewRequest("POST", "/share/view-state/TheDataSetID/shared-335", bytes.NewReader([]byte{}))
resp = executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

404
331 not found

404
332 not found

400
Cannot share workspaces if they reference non-shared objects

200
"334 shared"

400
Cannot share a shared ID
Example (ViewStateHandler_ShareViewState_AutoShare)

Shares a view state, with automatic sharing of referenced items turned on

var mockS3 awsutil.MockS3Client
defer mockS3.FinishTest()
mockS3.ExpGetObjectInput = []s3.GetObjectInput{
	// Test 1
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String(viewStateS3Path + "Workspaces/222.json"),
	},

	// Getting ROIs to be able to share...
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/TheDataSetID/ROI.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/TheDataSetID/ROI.json"),
	},
	// Getting expressions to be able to share...
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/DataExpressions.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/DataExpressions.json"),
	},
	// Getting rgb mixes
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/600f2a0806b6c70071d3d174/RGBMixes.json"),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/RGBMixes.json"),
	},
}

mockS3.QueuedGetObjectOutput = []*s3.GetObjectOutput{
	{
		// View state that references non-shared IDs. We want to make sure it returns the right ones and
		// count, so we return multiple IDs here:
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
				 "viewState": {
					"contextImages": { "0": { "mapLayers": [ { "expressionID": "rgbmix-123", "opacity": 1, "visible": true } ] } },
					"quantification": {"appliedQuantID": "shared-quant123"},
					"binaryPlots": { "44": { "expressionIDs": ["shared-expr", "expr1"], "visibleROIs": ["shared-roi"] } },
					"ternaryPlots": { "66": { "expressionIDs": ["shared-expr2"], "visibleROIs": ["roi2"] } }
				 },
				 "name": "222",
				"description": "the description of 222"
			}`))),
	},

	// ROIs
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"roi2": {
		"name": "Dark patch 2",
		"description": "The second dark patch",
		"locationIndexes": [4, 55, 394],
		"creator": { "name": "Peter", "user_id": "u123" }
	}
}`))),
	},
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"roi": {
		"name": "Shared patch 2",
		"shared": true,
		"description": "The shared patch",
		"locationIndexes": [4, 55, 394],
		"creator": { "name": "PeterN", "user_id": "u123" }
	}
}`))),
	},
	// Expressions
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"abc123": {
		"name": "Temp data",
		"expression": "housekeeping(\"something\")",
		"type": "All",
		"comments": "comments for abc123 expression",
		"creator": {
			"user_id": "444",
			"name": "Niko",
			"email": "niko@spicule.co.uk"
		}
	},
	"expr1": {
		"name": "Calcium weight%",
		"expression": "element(\"Ca\", \"%\")",
		"type": "All",
		"comments": "comments for expr1",
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "peter@spicule.co.uk"
		}
	}
}`))),
	},
	nil, // simulate missing shared expressions file...
	// User RGB mixes
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"rgbmix-123": {
		"name": "Ca-Ti-Al ratios",
		"red": {
			"expressionID": "expr-for-Ca",
			"rangeMin": 1.5,
			"rangeMax": 4.3
		},
		"green": {
			"expressionID": "expr-for-Al",
			"rangeMin": 2.5,
			"rangeMax": 5.3
		},
		"blue": {
			"expressionID": "expr-for-Ti",
			"rangeMin": 3.5,
			"rangeMax": 6.3
		},
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "niko@spicule.co.uk"
		}
	}
}`))),
	},
	// Shared RGB mixes
	{
		Body: ioutil.NopCloser(bytes.NewReader([]byte(`{
	"380": {
		"name": "Fe-Ca-Al ratios",
		"red": {
			"expressionID": "expr-for-Fe",
			"rangeMin": 2.5,
			"rangeMax": 4.3
		},
		"green": {
			"expressionID": "expr-for-Ca",
			"rangeMin": 3.5,
			"rangeMax": 5.3
		},
		"blue": {
			"expressionID": "expr-for-Ti",
			"rangeMin": 3.5,
			"rangeMax": 6.3
		},
		"shared": true,
		"creator": {
			"user_id": "999",
			"name": "Peter N",
			"email": "niko@spicule.co.uk"
		}
	}
}`))),
	},
}

// NOTE: PUT expected JSON needs to have spaces not tabs
mockS3.ExpPutObjectInput = []s3.PutObjectInput{
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/TheDataSetID/ROI.json"), Body: bytes.NewReader([]byte(`{
    "roi": {
        "name": "Shared patch 2",
        "locationIndexes": [
            4,
            55,
            394
        ],
        "description": "The shared patch",
        "shared": true,
        "creator": {
            "name": "PeterN",
            "user_id": "u123",
            "email": ""
        }
    },
    "roi2(sh)": {
        "name": "Dark patch 2",
        "locationIndexes": [
            4,
            55,
            394
        ],
        "description": "The second dark patch",
        "shared": true,
        "creator": {
            "name": "Peter",
            "user_id": "u123",
            "email": ""
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/DataExpressions.json"), Body: bytes.NewReader([]byte(`{
    "expr1(sh)": {
        "name": "Calcium weight%",
        "expression": "element(\"Ca\", \"%\")",
        "type": "All",
        "comments": "comments for expr1",
        "shared": true,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "peter@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/RGBMixes.json"), Body: bytes.NewReader([]byte(`{
    "380": {
        "name": "Fe-Ca-Al ratios",
        "red": {
            "expressionID": "expr-for-Fe",
            "rangeMin": 2.5,
            "rangeMax": 4.3
        },
        "green": {
            "expressionID": "expr-for-Ca",
            "rangeMin": 3.5,
            "rangeMax": 5.3
        },
        "blue": {
            "expressionID": "expr-for-Ti",
            "rangeMin": 3.5,
            "rangeMax": 6.3
        },
        "shared": true,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    },
    "rgbmix-123roi": {
        "name": "Ca-Ti-Al ratios",
        "red": {
            "expressionID": "expr-for-Ca",
            "rangeMin": 1.5,
            "rangeMax": 4.3
        },
        "green": {
            "expressionID": "expr-for-Al",
            "rangeMin": 2.5,
            "rangeMax": 5.3
        },
        "blue": {
            "expressionID": "expr-for-Ti",
            "rangeMin": 3.5,
            "rangeMax": 6.3
        },
        "shared": true,
        "creator": {
            "name": "Peter N",
            "user_id": "999",
            "email": "niko@spicule.co.uk"
        }
    }
}`)),
	},
	{
		Bucket: aws.String(UsersBucketForUnitTest), Key: aws.String("UserContent/shared/TheDataSetID/ViewState/Workspaces/222.json"), Body: bytes.NewReader([]byte(`{
    "viewState": {
        "analysisLayout": {
            "topWidgetSelectors": [],
            "bottomWidgetSelectors": []
        },
        "spectrum": {
            "panX": 0,
            "panY": 0,
            "zoomX": 1,
            "zoomY": 1,
            "spectrumLines": [],
            "logScale": true,
            "xrflines": [],
            "showXAsEnergy": false,
            "energyCalibration": []
        },
        "contextImages": {
            "0": {
                "panX": 0,
                "panY": 0,
                "zoomX": 0,
                "zoomY": 0,
                "showPoints": false,
                "showPointBBox": false,
                "pointColourScheme": "",
                "pointBBoxColourScheme": "",
                "contextImage": "",
                "contextImageSmoothing": "",
                "mapLayers": [
                    {
                        "expressionID": "rgbmix-123",
                        "opacity": 1,
                        "visible": true,
                        "displayValueRangeMin": 0,
                        "displayValueRangeMax": 0,
                        "displayValueShading": ""
                    }
                ],
                "roiLayers": null,
                "elementRelativeShading": false,
                "brightness": 0,
                "rgbuChannels": "",
                "unselectedOpacity": 0,
                "unselectedGrayscale": false,
                "colourRatioMin": 0,
                "colourRatioMax": 0,
                "removeTopSpecularArtifacts": false,
                "removeBottomSpecularArtifacts": false
            }
        },
        "histograms": {},
        "chordDiagrams": {},
        "ternaryPlots": {
            "66": {
                "showMmol": false,
                "expressionIDs": [
                    "shared-expr2"
                ],
                "visibleROIs": [
                    "shared-roi2(sh)"
                ]
            }
        },
        "binaryPlots": {
            "44": {
                "showMmol": false,
                "expressionIDs": [
                    "shared-expr",
                    "shared-expr1(sh)"
                ],
                "visibleROIs": [
                    "shared-roi"
                ]
            }
        },
        "tables": {},
        "roiQuantTables": {},
        "variograms": {},
        "spectrums": {},
        "rgbuPlots": {},
        "singleAxisRGBU": {},
        "rgbuImages": {},
        "parallelograms": {},
        "rois": {
            "roiColours": {}
        },
        "quantification": {
            "appliedQuantID": "shared-quant123"
        },
        "selection": {
            "roiID": "",
            "roiName": "",
            "locIdxs": []
        }
    },
    "name": "222",
    "description": "the description of 222",
    "shared": true,
    "creator": {
        "name": "Niko Bellic",
        "user_id": "600f2a0806b6c70071d3d174",
        "email": "niko@spicule.co.uk"
    }
}`)),
	},
}
mockS3.QueuedPutObjectOutput = []*s3.PutObjectOutput{
	{},
	{},
	{},
	{},
}

var idGen MockIDGenerator
idGen.ids = []string{"roi2(sh)", "expr1(sh)", "123roi"}
svcs := MakeMockSvcs(&mockS3, &idGen, nil, nil, nil)
apiRouter := MakeRouter(svcs)

// User file not there, should say not found
req, _ := http.NewRequest("POST", "/share/view-state/TheDataSetID/222?auto-share=true", bytes.NewReader([]byte{}))
resp := executeRequest(req, apiRouter.Router)

fmt.Println(resp.Code)
fmt.Println(resp.Body)
Output:

200
"222 shared"

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func InitAuth0ManagementAPI

func InitAuth0ManagementAPI(cfg config.APIConfig) (*management.Management, error)

InitAuth0ManagementAPI - bootstrap auth0

Types

type AppData

type AppData struct {
	Topics []apiNotifications.Topics `json:"topics"`
}

AppData - App data type for JSON conversion

type ByAtomicNumber

type ByAtomicNumber []elementLines

ByAtomicNumber Atomic Numbering

func (ByAtomicNumber) Len

func (a ByAtomicNumber) Len() int

func (ByAtomicNumber) Less

func (a ByAtomicNumber) Less(i, j int) bool

func (ByAtomicNumber) Swap

func (a ByAtomicNumber) Swap(i, j int)

type ByJobID

type ByJobID []quantModel.JobSummaryItem

ByJobID sorting of quant summaries

func (ByJobID) Len

func (a ByJobID) Len() int

func (ByJobID) Less

func (a ByJobID) Less(i, j int) bool

func (ByJobID) Swap

func (a ByJobID) Swap(i, j int)

type ByReferencedID

type ByReferencedID []referencedIDItem

ByReferencedID To sort referenced IDs by ID field

func (ByReferencedID) Len

func (a ByReferencedID) Len() int

func (ByReferencedID) Less

func (a ByReferencedID) Less(i, j int) bool

func (ByReferencedID) Swap

func (a ByReferencedID) Swap(i, j int)

type ChannelConfig

type ChannelConfig struct {
	ExpressionID string  `json:"expressionID"`
	RangeMin     float32 `json:"rangeMin"`
	RangeMax     float32 `json:"rangeMax"`

	// We used to store this, now only here for reading in old files (backwards compatible). PIXLISE then converts it to an ExpressionID when saving again
	Element string `json:"element,omitempty"`
}

RGBMixInput - only public so we can use it embedded in dataExpression

type ComponentVersion

type ComponentVersion struct {
	Component        string `json:"component"`
	Version          string `json:"version"`
	BuildUnixTimeSec int32  `json:"build-unix-time-sec"`
}

ComponentVersion is getting versions of stuff in API, public because it's used in integration test

type ComponentVersionsGetResponse

type ComponentVersionsGetResponse struct {
	Components []ComponentVersion `json:"components"`
}

ComponentVersionsGetResponse is wrapper of above

type Config

type Config struct {
	Cell    string `json:"cell"`
	Methods Method `json:"method"`
}

Config - List of configurations from App Metadata.

type CustomResponse

type CustomResponse struct {
	LogID string
}

type GlobalData

type GlobalData struct {
	GlobalContent string `json:"content"`
	GlobalSubject string `json:"subject"`
}

GlobalData - JSON Data for global emails

type HintsData

type HintsData struct {
	Hints []string `json:"hints"`
}

HintsData - Hints Object

type LoggerMiddleware

type LoggerMiddleware struct {
	*services.APIServices
	JwtValidator api.JWTInterface
}

func (*LoggerMiddleware) Middleware

func (h *LoggerMiddleware) Middleware(next http.Handler) http.Handler

type Method

type Method struct {
	UI    bool `json:"ui"`
	Sms   bool `json:"sms"`
	Email bool `json:"email"`
}

Method - Subscription methods

type MultiQuantificationComparisonRequest

type MultiQuantificationComparisonRequest struct {
	QuantIDs            []string `json:"quantIDs"`
	RemainingPointsPMCs []int    `json:"remainingPointsPMCs"`
}

type MultiQuantificationComparisonResponse

type MultiQuantificationComparisonResponse struct {
	RoiID       string       `json:"roiID"`
	QuantTables []QuantTable `json:"quantTables"`
}

type QuantCombineItem

type QuantCombineItem struct {
	RoiID            string `json:"roiID"`
	QuantificationID string `json:"quantificationID"`
}

Users specify a range of ROIs, with a quant for each. Order matters, this is how they will be combined

type QuantCombineList

type QuantCombineList struct {
	RoiZStack []QuantCombineItem `json:"roiZStack"`
}

type QuantCombineRequest

type QuantCombineRequest struct {
	RoiZStack   []QuantCombineItem `json:"roiZStack"`
	Name        string             `json:"name"`
	Description string             `json:"description"`
	SummaryOnly bool               `json:"summaryOnly"`
}

type QuantCombineSummaryResponse

type QuantCombineSummaryResponse struct {
	Detectors      []string              `json:"detectors"`
	WeightPercents map[string]SummaryRow `json:"weightPercents"`
}

type QuantItem

type QuantItem struct {
	RTT      int32
	PMC      int32
	SCLK     int32
	Filename string
	LiveTime int32
	RoiID    string
	Columns  map[string]float64
	ROIName  string
}

type QuantListingResponse

type QuantListingResponse struct {
	Summaries    []quantModel.JobSummaryItem `json:"summaries"`
	BlessedQuant *quantModel.BlessFileItem   `json:"blessedQuant"`
}

type QuantTable

type QuantTable struct {
	QuantID   string `json:"quantID"`
	QuantName string `json:"quantName"`

	ElementWeights map[string]float32 `json:"elementWeights"`
}

type RGBMixInput

type RGBMixInput struct {
	Name string `json:"name"`

	Red   ChannelConfig `json:"red"`
	Green ChannelConfig `json:"green"`
	Blue  ChannelConfig `json:"blue"`
}

type SummaryRow

type SummaryRow struct {
	Values   []float32 `json:"values"`
	ROIIDs   []string  `json:"roiIDs"`
	ROINames []string  `json:"roiNames"`
}

type TestData

type TestData struct {
	TestType    string `json:"type"`
	TestContact string `json:"contact"`
}

TestData - JSON Data for test emails

type UserDiffractionPeak

type UserDiffractionPeak struct {
	PMC int32   `json:"pmc"`
	KeV float32 `json:"keV"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL