Skip to content

Commit

Permalink
Add endpoint for updating event type (#455)
Browse files Browse the repository at this point in the history
  • Loading branch information
mthenw authored Jun 7, 2018
1 parent 50ebe47 commit b01bbf9
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 41 deletions.
13 changes: 10 additions & 3 deletions event/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,20 @@ func (e ErrEventTypeValidation) Error() string {
return fmt.Sprintf("Event Type doesn't validate. Validation error: %s", e.Message)
}

// ErrEventTypeHasSubscriptionsError occurs when there are subscription for the event type.
type ErrEventTypeHasSubscriptionsError struct{}
// ErrEventTypeHasSubscriptions occurs when there are subscription for the event type.
type ErrEventTypeHasSubscriptions struct{}

func (e ErrEventTypeHasSubscriptionsError) Error() string {
func (e ErrEventTypeHasSubscriptions) Error() string {
return fmt.Sprintf("Event type cannot be deleted because there are subscriptions using it.")
}

// ErrAuthorizerDoesNotExists occurs when there authorizer function doesn't exists.
type ErrAuthorizerDoesNotExists struct{}

func (e ErrAuthorizerDoesNotExists) Error() string {
return fmt.Sprintf("Authorizer function doesn't exists.")
}

// ErrParsingCloudEvent occurs when payload is not valid CloudEvent.
type ErrParsingCloudEvent struct {
Message string
Expand Down
1 change: 1 addition & 0 deletions event/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ type Service interface {
CreateEventType(eventType *Type) (*Type, error)
GetEventType(space string, name TypeName) (*Type, error)
GetEventTypes(space string) (Types, error)
UpdateEventType(newEventType *Type) (*Type, error)
DeleteEventType(space string, name TypeName) error
}
6 changes: 3 additions & 3 deletions function/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ func (e ErrFunctionError) Error() string {
return fmt.Sprintf("Function call failed because of runtime error. Error: %s", e.Original)
}

// ErrFunctionHasSubscriptionsError occurs when function with subscription is being deleted.
type ErrFunctionHasSubscriptionsError struct{}
// ErrFunctionHasSubscriptions occurs when function with subscription is being deleted.
type ErrFunctionHasSubscriptions struct{}

func (e ErrFunctionHasSubscriptionsError) Error() string {
func (e ErrFunctionHasSubscriptions) Error() string {
return fmt.Sprintf("Function cannot be deleted because it's subscribed to a least one event.")
}
42 changes: 40 additions & 2 deletions httpapi/httpapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (h HTTPAPI) RegisterRoutes(router *httprouter.Router) {
router.GET("/v1/spaces/:space/eventtypes", h.listEventTypes)
router.GET("/v1/spaces/:space/eventtypes/:name", h.getEventType)
router.POST("/v1/spaces/:space/eventtypes", h.createEventType)
router.PUT("/v1/spaces/:space/eventtypes/:name", h.updateEventType)
router.DELETE("/v1/spaces/:space/eventtypes/:name", h.deleteEventType)

router.GET("/v1/spaces/:space/functions", h.listFunctions)
Expand Down Expand Up @@ -129,6 +130,43 @@ func (h HTTPAPI) createEventType(w http.ResponseWriter, r *http.Request, params
metricConfigRequests.WithLabelValues(eventType.Space, "eventtype", "create").Inc()
}

func (h HTTPAPI) updateEventType(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)

eventType := &event.Type{}
dec := json.NewDecoder(r.Body)
err := dec.Decode(eventType)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
validationErr := event.ErrEventTypeValidation{Message: err.Error()}
encoder.Encode(&Response{Errors: []Error{{Message: validationErr.Error()}}})
return
}

eventType.Space = params.ByName("space")
eventType.Name = event.TypeName(params.ByName("name"))
output, err := h.EventTypes.UpdateEventType(eventType)
if err != nil {
if _, ok := err.(*event.ErrEventTypeNotFound); ok {
w.WriteHeader(http.StatusNotFound)
} else if _, ok := err.(*event.ErrEventTypeValidation); ok {
w.WriteHeader(http.StatusBadRequest)
} else if _, ok := err.(*event.ErrAuthorizerDoesNotExists); ok {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusInternalServerError)
}

encoder.Encode(&Response{Errors: []Error{{Message: err.Error()}}})
} else {
w.WriteHeader(http.StatusOK)
encoder.Encode(output)
}

metricConfigRequests.WithLabelValues(eventType.Space, "eventtype", "update").Inc()
}

func (h HTTPAPI) deleteEventType(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
Expand All @@ -138,7 +176,7 @@ func (h HTTPAPI) deleteEventType(w http.ResponseWriter, r *http.Request, params
if err != nil {
if _, ok := err.(*event.ErrEventTypeNotFound); ok {
w.WriteHeader(http.StatusNotFound)
} else if _, ok := err.(*event.ErrEventTypeHasSubscriptionsError); ok {
} else if _, ok := err.(*event.ErrEventTypeHasSubscriptions); ok {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -270,7 +308,7 @@ func (h HTTPAPI) deleteFunction(w http.ResponseWriter, r *http.Request, params h
if err != nil {
if _, ok := err.(*function.ErrFunctionNotFound); ok {
w.WriteHeader(http.StatusNotFound)
} else if _, ok := err.(*function.ErrFunctionHasSubscriptionsError); ok {
} else if _, ok := err.(*function.ErrFunctionHasSubscriptions); ok {
w.WriteHeader(http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusInternalServerError)
Expand Down
124 changes: 121 additions & 3 deletions httpapi/httpapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,86 @@ func TestCreateEventType(t *testing.T) {
})
}

func TestUpdateEventType(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
router, eventTypes, _, _ := setup(ctrl)

typePayload := []byte(`{"name":"test.event","space":"test1"}`)

t.Run("event type updated", func(t *testing.T) {
eventType := &event.Type{Space: "default", Name: event.TypeName("test.event")}
eventTypes.EXPECT().UpdateEventType(eventType).Return(eventType, nil)

resp := request(router, http.MethodPut, "/v1/spaces/default/eventtypes/test.event", typePayload)

returnedType := &event.Type{}
json.Unmarshal(resp.Body.Bytes(), returnedType)
assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, "application/json", resp.Header().Get("Content-Type"))
assert.Equal(t, event.TypeName("test.event"), returnedType.Name)
assert.Equal(t, "default", returnedType.Space)
})

t.Run("event type doesn't exists", func(t *testing.T) {
eventTypes.EXPECT().UpdateEventType(gomock.Any()).
Return(nil, &event.ErrEventTypeNotFound{Name: event.TypeName("test.event")})

resp := request(router, http.MethodPut, "/v1/spaces/default/eventtypes/test.event", typePayload)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusNotFound, resp.Code)
assert.Equal(t, `Event Type "test.event" not found.`, httpresp.Errors[0].Message)
})

t.Run("authorizer doesn't exists error", func(t *testing.T) {
eventTypes.EXPECT().UpdateEventType(gomock.Any()).
Return(nil, &event.ErrAuthorizerDoesNotExists{})

payload := []byte(`{"name":"test"}`)
resp := request(router, http.MethodPut, "/v1/spaces/default/eventtypes/test.event", payload)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusBadRequest, resp.Code)
assert.Equal(t, "Authorizer function doesn't exists.", httpresp.Errors[0].Message)
})

t.Run("validation error", func(t *testing.T) {
eventTypes.EXPECT().UpdateEventType(gomock.Any()).
Return(nil, &event.ErrEventTypeValidation{Message: "some error"})

payload := []byte(`{"name":"test"}`)
resp := request(router, http.MethodPut, "/v1/spaces/default/eventtypes/test.event", payload)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusBadRequest, resp.Code)
assert.Equal(t, "Event Type doesn't validate. Validation error: some error", httpresp.Errors[0].Message)
})

t.Run("malformed JSON", func(t *testing.T) {
resp := request(router, http.MethodPut, "/v1/spaces/default/eventtypes/test.event", []byte("{"))

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusBadRequest, resp.Code)
assert.Equal(t, "Event Type doesn't validate. Validation error: unexpected EOF", httpresp.Errors[0].Message)
})

t.Run("internal error", func(t *testing.T) {
eventTypes.EXPECT().UpdateEventType(gomock.Any()).Return(nil, errors.New("processing error"))

resp := request(router, http.MethodPut, "/v1/spaces/default/eventtypes/test.event", typePayload)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusInternalServerError, resp.Code)
assert.Equal(t, `processing error`, httpresp.Errors[0].Message)
})
}

func TestDeleteEventType(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -181,7 +261,7 @@ func TestDeleteEventType(t *testing.T) {
})

t.Run("event type has subscriptions", func(t *testing.T) {
eventTypes.EXPECT().DeleteEventType(gomock.Any(), gomock.Any()).Return(&event.ErrEventTypeHasSubscriptionsError{})
eventTypes.EXPECT().DeleteEventType(gomock.Any(), gomock.Any()).Return(&event.ErrEventTypeHasSubscriptions{})

resp := request(router, http.MethodDelete, "/v1/spaces/default/eventtypes/test.event", nil)

Expand All @@ -191,7 +271,7 @@ func TestDeleteEventType(t *testing.T) {
assert.Equal(t, "Event type cannot be deleted because there are subscriptions using it.", httpresp.Errors[0].Message)
})

t.Run("function not found", func(t *testing.T) {
t.Run("event type not found", func(t *testing.T) {
eventTypes.EXPECT().DeleteEventType(gomock.Any(), gomock.Any()).Return(&event.ErrEventTypeNotFound{Name: event.TypeName("test.event")})

resp := request(router, http.MethodDelete, "/v1/spaces/default/eventtypes/test.event", nil)
Expand Down Expand Up @@ -388,7 +468,7 @@ func TestDeleteFunction(t *testing.T) {
})

t.Run("function has subscriptions", func(t *testing.T) {
functions.EXPECT().DeleteFunction(gomock.Any(), gomock.Any()).Return(&function.ErrFunctionHasSubscriptionsError{})
functions.EXPECT().DeleteFunction(gomock.Any(), gomock.Any()).Return(&function.ErrFunctionHasSubscriptions{})

resp := request(router, http.MethodDelete, "/v1/spaces/default/functions/func1", nil)

Expand Down Expand Up @@ -520,6 +600,44 @@ func TestUpdateSubscription(t *testing.T) {
})
}

func TestDeleteSubscription(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
router, _, _, subscriptions := setup(ctrl)

t.Run("subscription deleted", func(t *testing.T) {
subscriptions.EXPECT().DeleteSubscription("default", subscription.ID("testid")).Return(nil)

resp := request(router, http.MethodDelete, "/v1/spaces/default/subscriptions/testid", nil)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusNoContent, resp.Code)
})

t.Run("subscriptions not found", func(t *testing.T) {
subscriptions.EXPECT().DeleteSubscription(gomock.Any(), gomock.Any()).Return(&subscription.ErrSubscriptionNotFound{ID: subscription.ID("testid")})

resp := request(router, http.MethodDelete, "/v1/spaces/default/subscriptions/testid", nil)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusNotFound, resp.Code)
assert.Equal(t, `Subscription "testid" not found.`, httpresp.Errors[0].Message)
})

t.Run("internal error", func(t *testing.T) {
subscriptions.EXPECT().DeleteSubscription(gomock.Any(), gomock.Any()).Return(errors.New("internal error"))

resp := request(router, http.MethodDelete, "/v1/spaces/default/subscriptions/testid", nil)

httpresp := &httpapi.Response{}
json.Unmarshal(resp.Body.Bytes(), httpresp)
assert.Equal(t, http.StatusInternalServerError, resp.Code)
assert.Equal(t, "internal error", httpresp.Errors[0].Message)
})
}

func request(router *httprouter.Router, method string, url string, payload []byte) *httptest.ResponseRecorder {
resp := httptest.NewRecorder()
body := bytes.NewReader(payload)
Expand Down
43 changes: 38 additions & 5 deletions libkv/eventtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (key EventTypeKey) String() string {

// CreateEventType creates event type in configuration.
func (service Service) CreateEventType(eventType *event.Type) (*event.Type, error) {
if err := service.validateEventType(eventType); err != nil {
if err := validateEventType(eventType); err != nil {
return nil, err
}

Expand All @@ -36,13 +36,13 @@ func (service Service) CreateEventType(eventType *event.Type) (*event.Type, erro
if eventType.AuthorizerID != nil {
function, _ := service.GetFunction(eventType.Space, *eventType.AuthorizerID)
if function == nil {
return nil, &event.ErrEventTypeValidation{Message: "Authorizer function doesn't exists."}
return nil, &event.ErrAuthorizerDoesNotExists{}
}
}

byt, err := json.Marshal(eventType)
if err != nil {
return nil, err
return nil, &event.ErrEventTypeValidation{Message: err.Error()}
}

err = service.EventTypeStore.Put(EventTypeKey{eventType.Space, eventType.Name}.String(), byt, nil)
Expand Down Expand Up @@ -97,6 +97,39 @@ func (service Service) GetEventTypes(space string) (event.Types, error) {
return event.Types(types), nil
}

// UpdateEventType updates subscription.
func (service Service) UpdateEventType(newEventType *event.Type) (*event.Type, error) {
if err := validateEventType(newEventType); err != nil {
return nil, err
}

_, err := service.GetEventType(newEventType.Space, newEventType.Name)
if err != nil {
return nil, err
}

if newEventType.AuthorizerID != nil {
function, _ := service.GetFunction(newEventType.Space, *newEventType.AuthorizerID)
if function == nil {
return nil, &event.ErrAuthorizerDoesNotExists{}
}
}

buf, err := json.Marshal(newEventType)
if err != nil {
return nil, &event.ErrEventTypeValidation{Message: err.Error()}
}

err = service.EventTypeStore.Put(EventTypeKey{newEventType.Space, newEventType.Name}.String(), buf, nil)
if err != nil {
return nil, err
}

service.Log.Debug("Event Type updated.", zap.Object("eventType", newEventType))

return newEventType, nil
}

// DeleteEventType deletes event type from the configuration.
func (service Service) DeleteEventType(space string, name event.TypeName) error {
subs, err := service.GetSubscriptions(space)
Expand All @@ -105,7 +138,7 @@ func (service Service) DeleteEventType(space string, name event.TypeName) error
}
for _, sub := range subs {
if name == sub.EventType {
return &event.ErrEventTypeHasSubscriptionsError{}
return &event.ErrEventTypeHasSubscriptions{}
}
}

Expand All @@ -119,7 +152,7 @@ func (service Service) DeleteEventType(space string, name event.TypeName) error
return nil
}

func (service Service) validateEventType(eventType *event.Type) error {
func validateEventType(eventType *event.Type) error {
if eventType.Space == "" {
eventType.Space = defaultSpace
}
Expand Down
Loading

0 comments on commit b01bbf9

Please sign in to comment.