diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 1b0fb002..69206fbd 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -250,15 +250,15 @@ var ( GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG = NewMessageType("GAL1155E: The id provided by the --id field cannot be an empty string.", 1155, STACK_TRACE_NOT_WANTED) GALASA_ERROR_LOGIN_ID_NOT_SUPPORTED = NewMessageType("GAL1156E: '%s' is not supported as a valid value. Valid values are 'me'.", 1156, STACK_TRACE_NOT_WANTED) GALASA_ERROR_DELETE_RUN_FAILED = NewMessageType("GAL1157E: An attempt to delete a run named '%s' failed. Cause is %s", 1157, STACK_TRACE_NOT_WANTED) - GALASA_ERROR_SERVER_DELETE_RUNS_FAILED = NewMessageType("GAL1157E: An attempt to delete a run named '%s' failed. Sending the delete request to the Galasa service failed. Cause is %v", 1157, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_SERVER_DELETE_RUNS_FAILED = NewMessageType("GAL1158E: An attempt to delete a run named '%s' failed. Sending the delete request to the Galasa service failed. Cause is %v", 1158, STACK_TRACE_NOT_WANTED) // 4 related but slightly different errors, when an HTTP response arrives from the Galasa server, and we can/can't parse the payload to get the message details out. GALASA_ERROR_DELETE_RUNS_NO_RESPONSE_CONTENT = NewMessageType("GAL1159E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server.", 1159, STACK_TRACE_NOT_WANTED) GALASA_ERROR_DELETE_RUNS_RESPONSE_PAYLOAD_UNREADABLE = NewMessageType("GAL1160E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server could not be read. Cause: %s", 1160, STACK_TRACE_NOT_WANTED) GALASA_ERROR_DELETE_RUNS_UNPARSEABLE_CONTENT = NewMessageType("GAL1161E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are not in a valid json format. Cause: '%s'", 1161, STACK_TRACE_NOT_WANTED) - GALASA_ERROR_DELETE_RUNS_SERVER_REPORTED_ERROR = NewMessageType("GAL1162E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are: %s", 1162, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_DELETE_RUNS_SERVER_REPORTED_ERROR = NewMessageType("GAL1162E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are: '%s'", 1162, STACK_TRACE_NOT_WANTED) - GALASA_ERROR_SERVER_DELETE_RUN_NOT_FOUND = NewMessageType("GAL1163E: The run named '%s' could not be deleted because it was not found by the Galasa service. Try listing runs using 'galasactl runs get' to identify the one you wish to delete", 1163, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_SERVER_DELETE_RUN_NOT_FOUND = NewMessageType("GAL1163E: The run named '%s' could not be deleted because it was not found by the Galasa service. Try listing runs using 'galasactl runs get' to identify the one you wish to delete", 1163, STACK_TRACE_NOT_WANTED) GALASA_ERROR_DELETE_RUNS_EXPLANATION_NOT_JSON = NewMessageType("GAL1164E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are not in the json format.", 1164, STACK_TRACE_NOT_WANTED) // Warnings... diff --git a/pkg/runs/runsDelete.go b/pkg/runs/runsDelete.go index 184ba4c8..f0fb696a 100644 --- a/pkg/runs/runsDelete.go +++ b/pkg/runs/runsDelete.go @@ -153,7 +153,7 @@ func httpResponseToGalasaError( if err == nil { // server returned galasa api error structure we understand. log.Printf("Failed - HTTP response - status code: '%v' server responded with error message: '%v' \n", statusCode, errorMsgReceivedFromApiServer) - err = galasaErrors.NewGalasaError(errorMsgReceivedFromApiServer, identifier, errorFromServer.Message) + err = galasaErrors.NewGalasaError(errorMsgReceivedFromApiServer, identifier, statusCode, errorFromServer.Message) } } } diff --git a/pkg/runs/runsDelete_test.go b/pkg/runs/runsDelete_test.go index a6debbe3..99e161a8 100644 --- a/pkg/runs/runsDelete_test.go +++ b/pkg/runs/runsDelete_test.go @@ -8,233 +8,316 @@ package runs import ( "encoding/json" "net/http" - "net/http/httptest" "strconv" "testing" "github.com/galasa-dev/cli/pkg/api" + "github.com/galasa-dev/cli/pkg/errors" "github.com/galasa-dev/cli/pkg/galasaapi" "github.com/galasa-dev/cli/pkg/utils" "github.com/stretchr/testify/assert" ) func createMockRun(runName string, runId string) galasaapi.Run { - run := *galasaapi.NewRun() - run.SetRunId(runId) - testStructure := *galasaapi.NewTestStructure() - testStructure.SetRunName(runName) + run := *galasaapi.NewRun() + run.SetRunId(runId) + testStructure := *galasaapi.NewTestStructure() + testStructure.SetRunName(runName) - run.SetTestStructure(testStructure) - return run + run.SetTestStructure(testStructure) + return run } func TestCanDeleteARun(t *testing.T) { - // Given... - runName := "J20" - runId := "J234567890" - - // Create the mock run to be deleted - runToDelete := createMockRun(runName, runId) - runToDeleteBytes, _ := json.Marshal(runToDelete) - runToDeleteJson := string(runToDeleteBytes) - - server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { - - assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) - acceptHeader := req.Header.Get("Accept") - if req.URL.Path == "/ras/runs" { - assert.Equal(t, "application/json", acceptHeader, "Expected Accept: application/json header, got: %s", acceptHeader) - WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) - } else if req.URL.Path == "/ras/runs/"+runId { - writer.WriteHeader(http.StatusNoContent) - } - })) - - console := utils.NewMockConsole() - apiServerUrl := server.URL - apiClient := api.InitialiseAPI(apiServerUrl) - mockTimeService := utils.NewMockTimeService() - - // When... - err := RunsDelete( - runName, - console, - apiServerUrl, - apiClient, - mockTimeService) - - // Then... - assert.Nil(t, err, "RunsDelete returned an unexpected error") - assert.Empty(t, console.ReadText(), "The console was written to on a successful deletion, it should be empty") + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusNoContent) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + + console := utils.NewMockConsole() + apiServerUrl := server.GetServerURL() + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService) + + // Then... + assert.Nil(t, err, "RunsDelete returned an unexpected error") + assert.Empty(t, console.ReadText(), "The console was written to on a successful deletion, it should be empty") } func TestDeleteNonExistantRunDisplaysError(t *testing.T) { - // Given... - nonExistantRunName := "runDoesNotExist123" - - server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { - assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) - acceptHeader := req.Header.Get("Accept") - if req.URL.Path == "/ras/runs" { - assert.Equal(t, "application/json", acceptHeader, "Expected Accept: application/json header, got: %s", acceptHeader) - WriteMockRasRunsResponse(t, writer, req, nonExistantRunName, []string{}) - } else { - assert.Fail(t, "An unexpected http request was issued to the test case.") - } - })) - - console := utils.NewMockConsole() - apiServerUrl := server.URL - apiClient := api.InitialiseAPI(apiServerUrl) - mockTimeService := utils.NewMockTimeService() - - // When... - err := RunsDelete( - nonExistantRunName, - console, - apiServerUrl, - apiClient, - mockTimeService) - - // Then... - assert.NotNil(t, err, "RunsDelete did not return an error but it should have") - consoleOutputText := console.ReadText() - assert.Contains(t, consoleOutputText, nonExistantRunName) - assert.Contains(t, consoleOutputText, "GAL1163E") - assert.Contains(t, consoleOutputText, "The run named 'runDoesNotExist123' could not be deleted") + // Given... + nonExistantRunName := "runDoesNotExist123" + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, nonExistantRunName, []string{}) + } + + interactions := []utils.HttpInteraction{ getRunsInteraction } + + server := utils.NewMockHttpServer(t, interactions) + + console := utils.NewMockConsole() + apiServerUrl := server.GetServerURL() + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := RunsDelete( + nonExistantRunName, + console, + apiServerUrl, + apiClient, + mockTimeService) + + // Then... + assert.NotNil(t, err, "RunsDelete did not return an error but it should have") + consoleOutputText := console.ReadText() + assert.Contains(t, consoleOutputText, nonExistantRunName) + assert.Contains(t, consoleOutputText, "GAL1163E") + assert.Contains(t, consoleOutputText, "The run named 'runDoesNotExist123' could not be deleted") } func TestRunsDeleteFailsWithNoExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { - // Given... - runName := "J20" - runId := "J234567890" - - // Create the mock run to be deleted - runToDelete := createMockRun(runName, runId) - runToDeleteBytes, _ := json.Marshal(runToDelete) - runToDeleteJson := string(runToDeleteBytes) - - server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { - - assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) - acceptHeader := req.Header.Get("Accept") - if req.URL.Path == "/ras/runs" { - assert.Equal(t, "application/json", acceptHeader, "Expected Accept: application/json header, got: %s", acceptHeader) - WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) - } else if req.URL.Path == "/ras/runs/"+runId { - writer.WriteHeader(http.StatusInternalServerError) - } - })) - - console := utils.NewMockConsole() - apiServerUrl := server.URL - apiClient := api.InitialiseAPI(apiServerUrl) - mockTimeService := utils.NewMockTimeService() - - // When... - err := RunsDelete( - runName, - console, - apiServerUrl, - apiClient, - mockTimeService) - - // Then... - assert.NotNil(t, err, "RunsDelete returned an unexpected error") - consoleText := console.ReadText() - assert.Contains(t, consoleText , runName) - assert.Contains(t, consoleText , "GAL1159E") + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService) + + // Then... + assert.NotNil(t, err, "RunsDelete returned an unexpected error") + consoleText := console.ReadText() + assert.Contains(t, consoleText , runName) + assert.Contains(t, consoleText , "GAL1159E") } func TestRunsDeleteFailsWithNonJsonContentTypeExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { - // Given... - runName := "J20" - runId := "J234567890" - - // Create the mock run to be deleted - runToDelete := createMockRun(runName, runId) - runToDeleteBytes, _ := json.Marshal(runToDelete) - runToDeleteJson := string(runToDeleteBytes) - - server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { - - assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) - acceptHeader := req.Header.Get("Accept") - if req.URL.Path == "/ras/runs" { - assert.Equal(t, "application/json", acceptHeader, "Expected Accept: application/json header, got: %s", acceptHeader) - WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) - } else if req.URL.Path == "/ras/runs/"+runId { - writer.WriteHeader(http.StatusInternalServerError) - writer.Header().Set("Content-Type", "application/notJsonOnPurpose") - writer.Write([]byte("something not json but non-zero-length.")) - } - })) - - console := utils.NewMockConsole() - apiServerUrl := server.URL - apiClient := api.InitialiseAPI(apiServerUrl) - mockTimeService := utils.NewMockTimeService() - - // When... - err := RunsDelete( - runName, - console, - apiServerUrl, - apiClient, - mockTimeService) - - // Then... - assert.NotNil(t, err, "RunsDelete returned an unexpected error") - consoleText := console.ReadText() - assert.Contains(t, consoleText, runName) - assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) - assert.Contains(t, consoleText, "GAL1164E") - assert.Contains(t, consoleText, "Error details from the server are not in the json format") + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) + writer.Header().Set("Content-Type", "application/notJsonOnPurpose") + writer.Write([]byte("something not json but non-zero-length.")) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + + console := utils.NewMockConsole() + apiServerUrl := server.GetServerURL() + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService) + + // Then... + assert.NotNil(t, err, "RunsDelete returned an unexpected error") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1164E") + assert.Contains(t, consoleText, "Error details from the server are not in the json format") } +func TestRunsDeleteFailsWithBadlyFormedJsonContentExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte(`{ "this": "isBadJson because it doesnt end in a close braces" `)) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + + console := utils.NewMockConsole() + apiServerUrl := server.GetServerURL() + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService) + + // Then... + assert.NotNil(t, err, "RunsDelete returned an unexpected error") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1161E") + assert.Contains(t, consoleText, "Error details from the server are not in a valid json format") + assert.Contains(t, consoleText, "Cause: 'unexpected end of JSON input'") +} -// func TestRunsDeleteFailsWithBadlyFormedJsonContentExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { -// // Given... -// runName := "J20" -// runId := "J234567890" - -// // Create the mock run to be deleted -// runToDelete := createMockRun(runName, runId) -// runToDeleteBytes, _ := json.Marshal(runToDelete) -// runToDeleteJson := string(runToDeleteBytes) - -// server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { - -// assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) -// acceptHeader := req.Header.Get("Accept") -// if req.URL.Path == "/ras/runs" { -// assert.Equal(t, "application/json", acceptHeader, "Expected Accept: application/json header, got: %s", acceptHeader) -// WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) -// } else if req.URL.Path == "/ras/runs/"+runId { -// writer.WriteHeader(http.StatusInternalServerError) -// writer.Header().Add("Content-Type", "application/json") -// writer.Write([]byte(`{ "this", "isBadJson because it doesnt end in a close braces" `)) -// } -// })) - -// console := utils.NewMockConsole() -// apiServerUrl := server.URL -// apiClient := api.InitialiseAPI(apiServerUrl) -// mockTimeService := utils.NewMockTimeService() - -// // When... -// err := RunsDelete( -// runName, -// console, -// apiServerUrl, -// apiClient, -// mockTimeService) - -// // Then... -// assert.NotNil(t, err, "RunsDelete returned an unexpected error") -// consoleText := console.ReadText() -// assert.Contains(t, consoleText, runName) -// assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) -// assert.Contains(t, consoleText, "Gxxxx") -// assert.Contains(t, consoleText, "Error details from the server are not in the json format") -// } +func TestRunsDeleteFailsWithValidErrorResponsePayloadGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + apiErrorCode := 5000 + apiErrorMessage := "this is an error from the API server" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + + apiError := errors.GalasaAPIError{ + Code: apiErrorCode, + Message: apiErrorMessage, + } + apiErrorBytes, _ := json.Marshal(apiError) + writer.Write(apiErrorBytes) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + + console := utils.NewMockConsole() + apiServerUrl := server.GetServerURL() + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService) + + // Then... + assert.NotNil(t, err, "RunsDelete returned an unexpected error") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1162E") + assert.Contains(t, consoleText, apiErrorMessage) +} diff --git a/pkg/utils/httpInteractionMock.go b/pkg/utils/httpInteractionMock.go new file mode 100644 index 00000000..ef776b02 --- /dev/null +++ b/pkg/utils/httpInteractionMock.go @@ -0,0 +1,77 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package utils + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// The implementation of a HTTP interaction that allows unit tests to define +// interactions with the Galasa API server, with methods to validate requests +// and a lambda to write HTTP responses (which can be overridden as desired) +type HttpInteraction struct { + ExpectedPath string + ExpectedHttpMethod string + + // An override-able function to write a HTTP response for this interaction + WriteHttpResponseLambda func(writer http.ResponseWriter, req *http.Request) +} + +func NewHttpInteraction(expectedPath string, expectedHttpMethod string) HttpInteraction { + httpInteraction := HttpInteraction{ + ExpectedPath: expectedPath, + ExpectedHttpMethod: expectedHttpMethod, + } + + httpInteraction.WriteHttpResponseLambda = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusOK) + } + + return httpInteraction +} + +func (interaction *HttpInteraction) ValidateRequest(t *testing.T, req *http.Request) { + assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) + assert.Equal(t, interaction.ExpectedHttpMethod, req.Method, "Actual HTTP request method did not match the expected method") + assert.Equal(t, interaction.ExpectedPath, req.URL.Path, "Actual request path did not match the expected path") +} + +//----------------------------------------------------------------------------- +// Wrapper of a mock HTTP server that uses HTTP interactions to handle requests +//----------------------------------------------------------------------------- +type MockHttpServer struct { + CurrentInteractionIndex int + Server *httptest.Server +} + +func NewMockHttpServer(t *testing.T, interactions []HttpInteraction) MockHttpServer { + mockHttpServer := MockHttpServer{} + mockHttpServer.CurrentInteractionIndex = 0 + + mockHttpServer.Server = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { + + currentInteractionIndex := &mockHttpServer.CurrentInteractionIndex + if *currentInteractionIndex >= len(interactions) { + assert.Fail(t, "Mock server received an unexpected request to '%s' when it should not have", req.URL.Path) + } else { + currentInteraction := interactions[*currentInteractionIndex] + currentInteraction.ValidateRequest(t, req) + currentInteraction.WriteHttpResponseLambda(writer, req) + + // The next request to the server should get the next interaction, so advance the index by one + *currentInteractionIndex++ + } + })) + return mockHttpServer +} + +func (mockServer *MockHttpServer) GetServerURL() string { + return mockServer.Server.URL +} \ No newline at end of file