package apikey import ( "bytes" "context" "encoding/json" "io" "net/http" "net/http/httptest" "os" "strings" "testing" "github.com/artifacthub/hub/internal/apikey" "github.com/artifacthub/hub/internal/handlers/helpers" "github.com/artifacthub/hub/internal/hub" "github.com/artifacthub/hub/internal/tests" "github.com/go-chi/chi/v5" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) const apiKeyID = "00000000-0000-0000-0000-000000000001" func TestMain(m *testing.M) { zerolog.SetGlobalLevel(zerolog.Disabled) os.Exit(m.Run()) } func TestAdd(t *testing.T) { akJSON := `{"name": "apikey1"}` ak := &hub.APIKey{} _ = json.Unmarshal([]byte(akJSON), &ak) t.Run("invalid input", func(t *testing.T) { testCases := []struct { description string akJSON string err error }{ { "no api key provided", "", nil, }, { "invalid json", "-", nil, }, { "missing name", `{}`, hub.ErrInvalidInput, }, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("POST", "/", strings.NewReader(tc.akJSON)) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) hw := newHandlersWrapper() if tc.err != nil { hw.am.On("Add", r.Context(), mock.Anything).Return(nil, tc.err) } hw.h.Add(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) hw.am.AssertExpectations(t) }) } }) t.Run("error adding api key", func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("POST", "/", strings.NewReader(akJSON)) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) hw := newHandlersWrapper() hw.am.On("Add", r.Context(), ak).Return(nil, tests.ErrFakeDB) hw.h.Add(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) hw.am.AssertExpectations(t) }) t.Run("api key added successfully", func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("POST", "/", strings.NewReader(akJSON)) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) hw := newHandlersWrapper() akOUT := &hub.APIKey{ APIKeyID: "apiKeyID", Secret: "secret", } hw.am.On("Add", r.Context(), ak).Return(akOUT, nil) hw.h.Add(w, r) resp := w.Result() defer resp.Body.Close() h := resp.Header data, _ := io.ReadAll(resp.Body) assert.Equal(t, http.StatusCreated, resp.StatusCode) assert.Equal(t, "application/json", h.Get("Content-Type")) assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control")) outputAKJSON, _ := json.Marshal(akOUT) assert.Equal(t, outputAKJSON, data) hw.am.AssertExpectations(t) }) } func TestDelete(t *testing.T) { rctx := &chi.Context{ URLParams: chi.RouteParams{ Keys: []string{"apiKeyID"}, Values: []string{apiKeyID}, }, } t.Run("error deleting api key", func(t *testing.T) { testCases := []struct { err error expectedStatusCode int }{ { hub.ErrInvalidInput, http.StatusBadRequest, }, { tests.ErrFakeDB, http.StatusInternalServerError, }, } for _, tc := range testCases { t.Run(tc.err.Error(), func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("DELETE", "/", nil) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx)) hw := newHandlersWrapper() hw.am.On("Delete", r.Context(), apiKeyID).Return(tc.err) hw.h.Delete(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, tc.expectedStatusCode, resp.StatusCode) hw.am.AssertExpectations(t) }) } }) t.Run("delete api key succeeded", func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("DELETE", "/", nil) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx)) hw := newHandlersWrapper() hw.am.On("Delete", r.Context(), apiKeyID).Return(nil) hw.h.Delete(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, http.StatusNoContent, resp.StatusCode) hw.am.AssertExpectations(t) }) } func TestGet(t *testing.T) { rctx := &chi.Context{ URLParams: chi.RouteParams{ Keys: []string{"apiKeyID"}, Values: []string{apiKeyID}, }, } t.Run("error getting api key", func(t *testing.T) { testCases := []struct { err error expectedStatusCode int }{ { hub.ErrInvalidInput, http.StatusBadRequest, }, { tests.ErrFakeDB, http.StatusInternalServerError, }, } for _, tc := range testCases { t.Run(tc.err.Error(), func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/", nil) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx)) hw := newHandlersWrapper() hw.am.On("GetJSON", r.Context(), apiKeyID).Return(nil, tc.err) hw.h.Get(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, tc.expectedStatusCode, resp.StatusCode) hw.am.AssertExpectations(t) }) } }) t.Run("api key get succeeded", func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/", nil) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx)) hw := newHandlersWrapper() hw.am.On("GetJSON", r.Context(), apiKeyID).Return([]byte("dataJSON"), nil) hw.h.Get(w, r) resp := w.Result() defer resp.Body.Close() h := resp.Header data, _ := io.ReadAll(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, "application/json", h.Get("Content-Type")) assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control")) assert.Equal(t, []byte("dataJSON"), data) hw.am.AssertExpectations(t) }) } func TestGetOwnedByUser(t *testing.T) { t.Run("error getting api keys owned by user", func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/?limit=10&offset=1", nil) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) hw := newHandlersWrapper() hw.am.On("GetOwnedByUserJSON", r.Context(), &hub.Pagination{ Limit: 10, Offset: 1, }).Return(nil, tests.ErrFakeDB) hw.h.GetOwnedByUser(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) hw.am.AssertExpectations(t) }) t.Run("get api keys owned by user succeeded", func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/?limit=10&offset=1", nil) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) hw := newHandlersWrapper() hw.am.On("GetOwnedByUserJSON", r.Context(), &hub.Pagination{ Limit: 10, Offset: 1, }).Return(&hub.JSONQueryResult{ Data: []byte("dataJSON"), TotalCount: 1, }, nil) hw.h.GetOwnedByUser(w, r) resp := w.Result() defer resp.Body.Close() h := resp.Header data, _ := io.ReadAll(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, h.Get(helpers.PaginationTotalCount), "1") assert.Equal(t, "application/json", h.Get("Content-Type")) assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control")) assert.Equal(t, []byte("dataJSON"), data) hw.am.AssertExpectations(t) }) } func TestUpdate(t *testing.T) { t.Run("invalid input", func(t *testing.T) { testCases := []struct { description string akJSON string err error }{ { "no api key provided", "", nil, }, { "invalid json", "-", nil, }, { "missing name", `{}`, hub.ErrInvalidInput, }, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("PUT", "/", strings.NewReader(tc.akJSON)) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) hw := newHandlersWrapper() if tc.err != nil { hw.am.On("Update", r.Context(), mock.Anything).Return(tc.err) } hw.h.Update(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode) hw.am.AssertExpectations(t) }) } }) t.Run("valid api key provided", func(t *testing.T) { ak := &hub.APIKey{ APIKeyID: apiKeyID, Name: "apikey1", } akJSON, _ := json.Marshal(ak) testCases := []struct { description string err error expectedStatusCode int }{ { "api key update succeeded", nil, http.StatusNoContent, }, { "error updating api key (db error)", tests.ErrFakeDB, http.StatusInternalServerError, }, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { t.Parallel() w := httptest.NewRecorder() r, _ := http.NewRequest("PUT", "/", bytes.NewReader(akJSON)) r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID")) rctx := &chi.Context{ URLParams: chi.RouteParams{ Keys: []string{"apiKeyID"}, Values: []string{apiKeyID}, }, } r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx)) hw := newHandlersWrapper() hw.am.On("Update", r.Context(), ak).Return(tc.err) hw.h.Update(w, r) resp := w.Result() defer resp.Body.Close() assert.Equal(t, tc.expectedStatusCode, resp.StatusCode) hw.am.AssertExpectations(t) }) } }) } type handlersWrapper struct { am *apikey.ManagerMock h *Handlers } func newHandlersWrapper() *handlersWrapper { am := &apikey.ManagerMock{} return &handlersWrapper{ am: am, h: NewHandlers(am), } }