Skip to content

Commit

Permalink
Initial mocking-out of io.ReadAll in runs delete, added runs delete t…
Browse files Browse the repository at this point in the history
…o readme

Signed-off-by: Eamonn Mansour <[email protected]>
  • Loading branch information
eamansour committed Sep 4, 2024
1 parent 7a5217d commit c06bf11
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 13 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,20 @@ galasactl runs get --name C1234 --format badFormatterName
```
For a complete list of supported parameters see [here](./docs/generated/galasactl_runs_get.md).

## runs delete

This command deletes a test run from an ecosystem's RAS. The name of the test run to delete can be provided to delete it along with any associated artifacts that have been stored.

### Examples

A run named "C1234" can be deleted using the following command:

```
galasactl runs delete --name C1234
```

A complete list of supported parameters for the `runs delete` command is available [here](./docs/generated/galasactl_runs_delete.md)

## runs download

This command downloads all artifacts for a test run that are stored in an ecosystem's RAS.
Expand Down
4 changes: 4 additions & 0 deletions pkg/cmd/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ func (factory *RealFactory) GetAuthenticator(apiServerUrl string, galasaHome spi
jwtCache := auth.NewJwtCache(factory.GetFileSystem(), galasaHome, factory.GetTimeService())
return auth.NewAuthenticator(apiServerUrl, factory.GetFileSystem(), galasaHome, factory.GetTimeService(), factory.GetEnvironment(), jwtCache)
}

func (*RealFactory) GetByteReader() spi.ByteReader {
return utils.NewByteReader()
}
3 changes: 3 additions & 0 deletions pkg/cmd/runsDelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ func (cmd *RunsDeleteCommand) executeRunsDelete(
var apiClient *galasaapi.APIClient
apiClient, err = authenticator.GetAuthenticatedAPIClient()

byteReader := factory.GetByteReader()

if err == nil {
// Call to process the command in a unit-testable way.
err = runs.RunsDelete(
Expand All @@ -143,6 +145,7 @@ func (cmd *RunsDeleteCommand) executeRunsDelete(
apiServerUrl,
apiClient,
timeService,
byteReader,
)
}
}
Expand Down
9 changes: 6 additions & 3 deletions pkg/runs/runsDelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package runs

import (
"context"
"io"
"log"
"net/http"

Expand All @@ -27,6 +26,7 @@ func RunsDelete(
apiServerUrl string,
apiClient *galasaapi.APIClient,
timeService spi.TimeService,
byteReader spi.ByteReader,
) error {
var err error

Expand All @@ -52,7 +52,7 @@ func RunsDelete(
if len(runs) == 0 {
err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_SERVER_DELETE_RUN_NOT_FOUND, runName)
} else {
err = deleteRuns(runs, apiClient)
err = deleteRuns(runs, apiClient, byteReader)
}
}

Expand All @@ -67,6 +67,7 @@ func RunsDelete(
func deleteRuns(
runs []galasaapi.Run,
apiClient *galasaapi.APIClient,
byteReader spi.ByteReader,
) error {
var err error

Expand All @@ -93,6 +94,7 @@ func deleteRuns(
err = httpResponseToGalasaError(
httpResponse,
runName,
byteReader,
galasaErrors.GALASA_ERROR_DELETE_RUNS_NO_RESPONSE_CONTENT,
galasaErrors.GALASA_ERROR_DELETE_RUNS_RESPONSE_PAYLOAD_UNREADABLE,
galasaErrors.GALASA_ERROR_DELETE_RUNS_UNPARSEABLE_CONTENT,
Expand All @@ -116,6 +118,7 @@ func deleteRuns(
func httpResponseToGalasaError(
response *http.Response,
identifier string,
byteReader spi.ByteReader,
errorMsgUnexpectedStatusCodeNoResponseBody *galasaErrors.MessageType,
errorMsgUnableToReadResponseBody *galasaErrors.MessageType,
errorMsgResponsePayloadInWrongFormat *galasaErrors.MessageType,
Expand All @@ -136,7 +139,7 @@ func httpResponseToGalasaError(
if contentType != "application/json" {
err = galasaErrors.NewGalasaError(errorMsgResponseContentTypeNotJson, identifier, statusCode)
} else {
responseBodyBytes, err = io.ReadAll(response.Body)
responseBodyBytes, err = byteReader.ReadAll(response.Body)
if err != nil {
err = galasaErrors.NewGalasaError(errorMsgUnableToReadResponseBody, identifier, statusCode, err.Error())
} else {
Expand Down
89 changes: 79 additions & 10 deletions pkg/runs/runsDelete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,16 @@ func TestCanDeleteARun(t *testing.T) {
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReader()

// When...
err := RunsDelete(
runName,
console,
apiServerUrl,
apiClient,
mockTimeService)
mockTimeService,
mockByteReader)

// Then...
assert.Nil(t, err, "RunsDelete returned an unexpected error")
Expand All @@ -94,14 +96,16 @@ func TestDeleteNonExistantRunDisplaysError(t *testing.T) {
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReader()

// When...
err := RunsDelete(
nonExistantRunName,
console,
apiServerUrl,
apiClient,
mockTimeService)
mockTimeService,
mockByteReader)

// Then...
assert.NotNil(t, err, "RunsDelete did not return an error but it should have")
Expand Down Expand Up @@ -144,17 +148,19 @@ func TestRunsDeleteFailsWithNoExplanationErrorPayloadGivesCorrectMessage(t *test
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReader()

// When...
err := RunsDelete(
runName,
console,
apiServerUrl,
apiClient,
mockTimeService)
mockTimeService,
mockByteReader)

// Then...
assert.NotNil(t, err, "RunsDelete returned an unexpected error")
assert.NotNil(t, err, "RunsDelete did not return an error but it should have")
consoleText := console.ReadText()
assert.Contains(t, consoleText , runName)
assert.Contains(t, consoleText , "GAL1159E")
Expand Down Expand Up @@ -195,17 +201,19 @@ func TestRunsDeleteFailsWithNonJsonContentTypeExplanationErrorPayloadGivesCorrec
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReader()

// When...
err := RunsDelete(
runName,
console,
apiServerUrl,
apiClient,
mockTimeService)
mockTimeService,
mockByteReader)

// Then...
assert.NotNil(t, err, "RunsDelete returned an unexpected error")
assert.NotNil(t, err, "RunsDelete did not return an error but it should have")
consoleText := console.ReadText()
assert.Contains(t, consoleText, runName)
assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError))
Expand Down Expand Up @@ -248,17 +256,19 @@ func TestRunsDeleteFailsWithBadlyFormedJsonContentExplanationErrorPayloadGivesCo
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReader()

// When...
err := RunsDelete(
runName,
console,
apiServerUrl,
apiClient,
mockTimeService)
mockTimeService,
mockByteReader)

// Then...
assert.NotNil(t, err, "RunsDelete returned an unexpected error")
assert.NotNil(t, err, "RunsDelete did not return an error but it should have")
consoleText := console.ReadText()
assert.Contains(t, consoleText, runName)
assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError))
Expand Down Expand Up @@ -310,20 +320,79 @@ func TestRunsDeleteFailsWithValidErrorResponsePayloadGivesCorrectMessage(t *test
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReader()

// When...
err := RunsDelete(
runName,
console,
apiServerUrl,
apiClient,
mockTimeService)
mockTimeService,
mockByteReader)

// Then...
assert.NotNil(t, err, "RunsDelete returned an unexpected error")
assert.NotNil(t, err, "RunsDelete did not return an error but it should have")
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)
}


func TestRunsDeleteFailsWithFailureToReadResponseBodyGivesCorrectMessage(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(`{}`))
}

interactions := []utils.HttpInteraction{
getRunsInteraction,
deleteRunsInteraction,
}

server := utils.NewMockHttpServer(t, interactions)
defer server.Server.Close()

console := utils.NewMockConsole()
apiServerUrl := server.Server.URL
apiClient := api.InitialiseAPI(apiServerUrl)
mockTimeService := utils.NewMockTimeService()
mockByteReader := utils.NewMockByteReaderAsMock(true)

// When...
err := RunsDelete(
runName,
console,
apiServerUrl,
apiClient,
mockTimeService,
mockByteReader)

// 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, "GAL1160E")
assert.Contains(t, consoleText, "GAL1160E")
assert.Contains(t, consoleText, "Error details from the server could not be read")
}
13 changes: 13 additions & 0 deletions pkg/spi/byteReader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package spi

import "io"

// An interface to allow for mocking out "io" package reading-related methods
type ByteReader interface {
ReadAll(reader io.Reader) ([]byte, error)
}
1 change: 1 addition & 0 deletions pkg/spi/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ type Factory interface {
GetStdErrConsole() Console
GetTimeService() TimeService
GetAuthenticator(apiServerUrl string, galasaHome GalasaHome) Authenticator
GetByteReader() ByteReader
}
24 changes: 24 additions & 0 deletions pkg/utils/byteReader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package utils

import (
"io"

"github.com/galasa-dev/cli/pkg/spi"
)

// Implementation of a byte reader to allow mocking out methods from the io package
type ByteReaderImpl struct {
}

func NewByteReader() spi.ByteReader {
return new(ByteReaderImpl)
}

func (*ByteReaderImpl) ReadAll(reader io.Reader) ([]byte, error) {
return io.ReadAll(reader)
}
39 changes: 39 additions & 0 deletions pkg/utils/byteReaderMock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package utils

import (
"errors"
"io"

"github.com/galasa-dev/cli/pkg/spi"
)

// Mock implementation of a byte reader to allow for simulating failed read operations
type MockByteReader struct {
throwReadError bool
}

func NewMockByteReaderAsMock(throwReadError bool) *MockByteReader {
return &MockByteReader{
throwReadError: throwReadError,
}
}

func NewMockByteReader() spi.ByteReader {
return NewMockByteReaderAsMock(false)
}

func (mockReader *MockByteReader) ReadAll(reader io.Reader) ([]byte, error) {
var err error
var bytes []byte
if mockReader.throwReadError {
err = errors.New("simulating a read failure")
} else {
bytes, err = io.ReadAll(reader)
}
return bytes, err
}
8 changes: 8 additions & 0 deletions pkg/utils/factoryMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type MockFactory struct {
StdErrConsole spi.Console
TimeService spi.TimeService
Authenticator spi.Authenticator
ByteReader spi.ByteReader
}

func NewMockFactory() *MockFactory {
Expand Down Expand Up @@ -72,3 +73,10 @@ func (factory *MockFactory) GetAuthenticator(apiServerUrl string, galasaHome spi
}
return factory.Authenticator
}

func (factory *MockFactory) GetByteReader() spi.ByteReader {
if factory.ByteReader == nil {
factory.ByteReader = NewMockByteReader()
}
return factory.ByteReader
}

0 comments on commit c06bf11

Please sign in to comment.