diff --git a/api/http/handler/helm/helm_install.go b/api/http/handler/helm/helm_install.go index 9d9d5b829..f60bf711c 100644 --- a/api/http/handler/helm/helm_install.go +++ b/api/http/handler/helm/helm_install.go @@ -61,8 +61,7 @@ func (handler *Handler) helmInstall(w http.ResponseWriter, r *http.Request) *htt return httperror.InternalServerError("Unable to install a chart", err) } - w.WriteHeader(http.StatusCreated) - return response.JSON(w, release) + return response.JSONWithStatus(w, release, http.StatusCreated) } func (p *installChartPayload) Validate(_ *http.Request) error { diff --git a/api/http/handler/users/user_create_access_token.go b/api/http/handler/users/user_create_access_token.go index a0be36cd0..62f790a37 100644 --- a/api/http/handler/users/user_create_access_token.go +++ b/api/http/handler/users/user_create_access_token.go @@ -50,7 +50,7 @@ type accessTokenResponse struct { // @produce json // @param id path int true "User identifier" // @param body body userAccessTokenCreatePayload true "details" -// @success 201 {object} accessTokenResponse "Created" +// @success 200 {object} accessTokenResponse "Created" // @failure 400 "Invalid request" // @failure 401 "Unauthorized" // @failure 403 "Permission denied" @@ -115,13 +115,12 @@ func (handler *Handler) userCreateAccessToken(w http.ResponseWriter, r *http.Req return httperror.InternalServerError("Internal Server Error", err) } - w.WriteHeader(http.StatusCreated) - return response.JSON(w, accessTokenResponse{rawAPIKey, *apiKey}) + return response.JSONWithStatus(w, accessTokenResponse{rawAPIKey, *apiKey}, http.StatusCreated) } -func (handler *Handler) usesInternalAuthentication(userid portainer.UserID) (bool, error) { - // userid 1 is the admin user and always uses internal auth - if userid == 1 { +func (handler *Handler) usesInternalAuthentication(userID portainer.UserID) (bool, error) { + // userID 1 is the admin user and always uses internal auth + if userID == 1 { return true, nil } diff --git a/pkg/libhttp/response/response.go b/pkg/libhttp/response/response.go index 250a9859d..f1d0b5832 100644 --- a/pkg/libhttp/response/response.go +++ b/pkg/libhttp/response/response.go @@ -14,7 +14,14 @@ import ( // JSON encodes data to rw in JSON format. Returns a pointer to a // HandlerError if encoding fails. func JSON(rw http.ResponseWriter, data interface{}) *httperror.HandlerError { + return JSONWithStatus(rw, data, http.StatusOK) +} + +// JSONWithStatus encodes data to rw in JSON format with a specific status code. +// Returns a pointer to a HandlerError if encoding fails. +func JSONWithStatus(rw http.ResponseWriter, data interface{}, status int) *httperror.HandlerError { rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(status) enc := json.NewEncoder(rw) enc.SetSortMapKeys(false) diff --git a/pkg/libhttp/response/response_test.go b/pkg/libhttp/response/response_test.go new file mode 100644 index 000000000..d55142150 --- /dev/null +++ b/pkg/libhttp/response/response_test.go @@ -0,0 +1,143 @@ +package response + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJSONWithStatus(t *testing.T) { + type TestData struct { + Message string `json:"message"` + } + + tests := []struct { + name string + data interface{} + status int + }{ + { + name: "Success", + data: TestData{Message: "Hello, World!"}, + status: http.StatusOK, + }, + { + name: "Internal Server Error", + data: TestData{Message: "Internal Server Error"}, + status: http.StatusInternalServerError, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + + httpErr := JSONWithStatus(recorder, test.data, test.status) + + assert.Nil(t, httpErr) + assert.Equal(t, test.status, recorder.Code) + assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + + var response TestData + err := json.Unmarshal(recorder.Body.Bytes(), &response) + + assert.NoError(t, err) + assert.Equal(t, test.data, response) + }) + } +} + +func TestJSON(t *testing.T) { + type TestData struct { + Message string `json:"message"` + } + + tests := []struct { + name string + data interface{} + status int + }{ + { + name: "Success", + data: TestData{Message: "Hello, World!"}, + status: http.StatusOK, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + + httpErr := JSONWithStatus(recorder, test.data, test.status) + + assert.Nil(t, httpErr) + assert.Equal(t, test.status, recorder.Code) + assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + + var response TestData + err := json.Unmarshal(recorder.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, test.data, response) + }) + } +} + +func TestYAML(t *testing.T) { + tests := []struct { + name string + data interface{} + expected string + invalid bool + }{ + { + name: "Success", + data: "key: value", + expected: "key: value", + }, + { + name: "Invalid Data", + data: 123, + expected: "", + invalid: true, + }, + { + name: "doesn't support an Object", + data: map[string]interface{}{ + "key": "value", + }, + expected: "", + invalid: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + + httpErr := YAML(recorder, test.data) + + if test.invalid { + assert.NotNil(t, httpErr) + assert.Equal(t, http.StatusInternalServerError, httpErr.StatusCode) + return + } + + assert.Nil(t, httpErr) + assert.Equal(t, http.StatusOK, recorder.Code) + assert.Equal(t, "text/yaml", recorder.Header().Get("Content-Type")) + assert.Equal(t, test.expected, recorder.Body.String()) + }) + } +} + +func TestEmpty(t *testing.T) { + recorder := httptest.NewRecorder() + + httpErr := Empty(recorder) + + assert.Nil(t, httpErr) + assert.Equal(t, http.StatusNoContent, recorder.Code) +}