Skip to content

Commit

Permalink
Test and refactor CreateRetrieveJob (#59)
Browse files Browse the repository at this point in the history
* Add docstring and a basic test

* Refactor `CreateRetrieveJob`

* Add more tests and fix how `CreateRetrieveJob` is called.

* Expand `TestHandleJobResponse`

* Fix an error message

* Updated JSON validation to match of expected JSON structure
  • Loading branch information
kavir1698 authored May 22, 2024
1 parent 3ccda9c commit 91c175f
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 17 deletions.
8 changes: 6 additions & 2 deletions cmd/datasetPublishDataRetrieve/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@ func main() {
color.Unset()
} else {
// create retrieve Job
jobId := datasetUtils.CreateRetrieveJob(client, APIServer, user, datasetList)
fmt.Println(jobId)
jobId, err := datasetUtils.CreateRetrieveJob(client, APIServer, user, datasetList)
if err != nil {
log.Fatal(err)
} else{
fmt.Println(jobId)
}
}
}
67 changes: 52 additions & 15 deletions datasetUtils/createRetrieveJob.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
"log"
"net/http"
"time"
"fmt"
)

func CreateRetrieveJob(client *http.Client, APIServer string, user map[string]string, datasetList []string) (jobId string) {
// important: define field with capital names and rename fields via 'json' constructs
// otherwise the marshaling will omit the fields !

func constructJobRequest(user map[string]string, datasetList []string) ([]byte, error) {
type datasetStruct struct {
Pid string `json:"pid"`
Files []string `json:"files"`
Expand All @@ -32,39 +30,78 @@ func CreateRetrieveJob(client *http.Client, APIServer string, user map[string]st
emptyfiles := make([]string, 0)

var dsMap []datasetStruct
for i := 0; i < len(datasetList); i++ {
dsMap = append(dsMap, datasetStruct{datasetList[i], emptyfiles})
for _, dataset := range datasetList {
dsMap = append(dsMap, datasetStruct{dataset, emptyfiles})
}
jobMap["datasetList"] = dsMap

// marshal to JSON
var bmm []byte
bmm, _ = json.Marshal(jobMap)
// fmt.Printf("Marshalled job description : %s\n", string(bmm))
return json.Marshal(jobMap)
}

// now send archive job request
func sendJobRequest(client *http.Client, APIServer string, user map[string]string, bmm []byte) (*http.Response, error) {
myurl := APIServer + "/Jobs?access_token=" + user["accessToken"]
req, err := http.NewRequest("POST", myurl, bytes.NewBuffer(bmm))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
return nil, err
}
defer resp.Body.Close()
return resp, nil
}

func handleJobResponse(resp *http.Response, user map[string]string) (string, error) {
if resp.StatusCode == 200 {
log.Println("Job response Status: okay")
log.Println("A confirmation email will be sent to", user["mail"])
decoder := json.NewDecoder(resp.Body)
var j Job
err := decoder.Decode(&j)
if err != nil {
log.Fatal("Could not decode id from job:", err)
return "", fmt.Errorf("could not decode id from job: %v", err)
}
return j.Id
return j.Id, nil
} else {
log.Println("Job response Status: there are problems:", resp.StatusCode)
return ""
return "", fmt.Errorf("Job response Status: there are problems: %d", resp.StatusCode)
}
}

/*
CreateRetrieveJob creates a job to retrieve a dataset from an API server.
Parameters:
- client: An *http.Client object that is used to send the HTTP request.
- APIServer: A string representing the URL of the API server.
- user: A map[string]string containing user information. It should have keys "mail", "username", and "accessToken".
- datasetList: A slice of strings representing the list of datasets to be retrieved.
The function constructs a job request with the provided parameters and sends it to the API server. If the job is successfully created, it returns the job ID as a string. If the job creation fails, it returns an empty string.
The function logs the status of the job creation and sends a confirmation email to the user if the job is successfully created.
Note: The function will terminate the program if it encounters an error while sending the HTTP request or decoding the job ID from the response.
*/
func CreateRetrieveJob(client *http.Client, APIServer string, user map[string]string, datasetList []string) (jobId string, err error) {
bmm, err := constructJobRequest(user, datasetList)
if err != nil {
return "", err
}

resp, err := sendJobRequest(client, APIServer, user, bmm)
if err != nil {
return "", err
}
defer resp.Body.Close()

jobId, err = handleJobResponse(resp, user)
if err != nil {
return "", err
}

return jobId, nil
}
187 changes: 187 additions & 0 deletions datasetUtils/createRetrieveJob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package datasetUtils

import (
"net/http"
"net/http/httptest"
"testing"
"encoding/json"
"reflect"
"sort"
)

// Checks if the function returns a job ID when it successfully creates a job.
func TestCreateRetrieveJob(t *testing.T) {
// Create a test server that always responds with a 200 status code and a job ID
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{"id": "12345"}`))
}))
defer server.Close()

// Create a test client that uses the test server
client := server.Client()

// Define the parameters for the CreateRetrieveJob function
APIServer := server.URL
user := map[string]string{
"mail": "[email protected]",
"username": "testuser",
"accessToken": "testtoken",
}
datasetList := []string{"dataset1", "dataset2"}

// Call the CreateRetrieveJob function
jobId, _ := CreateRetrieveJob(client, APIServer, user, datasetList)

// Check if the function returned a job ID
if jobId == "" {
t.Errorf("CreateRetrieveJob() returned an empty job ID, want non-empty")
}
}

// checks if the function returns a valid JSON byte array and no error when it's called with valid parameters.
func TestConstructJobRequest(t *testing.T) {
// Define the parameters for the constructJobRequest function
user := map[string]string{
"mail": "[email protected]",
"username": "testuser",
}
datasetList := []string{"dataset1", "dataset2"}

// Call the constructJobRequest function
bmm, err := constructJobRequest(user, datasetList)

// Check if the function returned an error
if err != nil {
t.Errorf("constructJobRequest() returned an error: %v", err)
}

// Check if the function returned a valid JSON byte array
var data map[string]interface{}
if err := json.Unmarshal(bmm, &data); err != nil {
t.Errorf("constructJobRequest() returned invalid JSON: %v", err)
}

// Remove the creationTime field from the actual JSON
delete(data, "creationTime")

// Define the expected data
expectedData := map[string]interface{}{
"emailJobInitiator": user["mail"],
"jobParams": map[string]interface{}{
"username": user["username"],
"destinationPath": "/archive/retrieve",
},
"datasetList": []interface{}{
map[string]interface{}{"pid": datasetList[0], "files": []interface{}{}},
map[string]interface{}{"pid": datasetList[1], "files": []interface{}{}},
},
"jobStatusMessage": "jobSubmitted",
"type": "retrieve",
}

// Compare individual fields
for key, expectedValue := range expectedData {
if actualValue, ok := data[key]; ok {
if key == "datasetList" {
// Assert the underlying type of actualValue and expectedValue to []interface{}
actualList, ok1 := actualValue.([]interface{})
expectedList, ok2 := expectedValue.([]interface{})
if !ok1 || !ok2 {
t.Errorf("constructJobRequest() returned unexpected type for key %v: got %T want %T", key, actualValue, expectedValue)
continue
}

// Sort the datasetList slice before comparing
sort.Slice(actualList, func(i, j int) bool {
return actualList[i].(map[string]interface{})["pid"].(string) < actualList[j].(map[string]interface{})["pid"].(string)
})
sort.Slice(expectedList, func(i, j int) bool {
return expectedList[i].(map[string]interface{})["pid"].(string) < expectedList[j].(map[string]interface{})["pid"].(string)
})

actualValue = actualList
expectedValue = expectedList
}
if !reflect.DeepEqual(actualValue, expectedValue) {
t.Errorf("constructJobRequest() returned unexpected JSON for key %v: got %v want %v", key, actualValue, expectedValue)
}
} else {
t.Errorf("constructJobRequest() did not return expected key in JSON: %v", key)
}
}
}

// Checks if the function returns a valid HTTP response and no error when it's called with valid parameters.
func TestSendJobRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{"id": "12345"}`))
}))
defer server.Close()

client := server.Client()
APIServer := server.URL
user := map[string]string{
"mail": "[email protected]",
"username": "testuser",
"accessToken": "testtoken",
}
bmm := []byte(`{"key": "value"}`)

resp, err := sendJobRequest(client, APIServer, user, bmm)

if err != nil {
t.Errorf("sendJobRequest() returned an error: %v", err)
}

if resp.StatusCode != 200 {
t.Errorf("sendJobRequest() returned status code %v, want 200", resp.StatusCode)
}
}

// Checks for a successful response, a response with a non-200 status code, and a response with invalid JSON.
func TestHandleJobResponse(t *testing.T) {
user := map[string]string{
"mail": "[email protected]",
"username": "testuser",
}

// Test successful response
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{"id": "12345"}`))
}))
client := server.Client()
resp, _ := client.Get(server.URL)
jobId, err := handleJobResponse(resp, user)
if err != nil {
t.Errorf("handleJobResponse() returned an error: %v", err)
}
if jobId != "12345" {
t.Errorf("handleJobResponse() returned job ID %v, want 12345", jobId)
}
server.Close()

// Test non-200 status code
server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusInternalServerError)
rw.Write([]byte(`{"id": "12345"}`))
}))
client = server.Client()
resp, _ = client.Get(server.URL)
jobId, err = handleJobResponse(resp, user)
if err == nil {
t.Errorf("handleJobResponse() did not return an error for non-200 status code")
}
server.Close()

// Test invalid JSON in response
server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`invalid JSON`))
}))
client = server.Client()
resp, _ = client.Get(server.URL)
jobId, err = handleJobResponse(resp, user)
if err == nil {
t.Errorf("handleJobResponse() did not return an error for invalid JSON")
}
server.Close()
}

0 comments on commit 91c175f

Please sign in to comment.