Merge pull request #13 from davidlick/post-produce-endpoint
Add POST /produce endpoint
This commit was merged in pull request #13.
This commit is contained in:
BIN
Binary file not shown.
+11
-1
@@ -9,6 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/davidlick/supermarket-api/internal/http"
|
||||
"github.com/davidlick/supermarket-api/internal/produce"
|
||||
"github.com/davidlick/supermarket-api/pkg/ramdb"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -34,7 +36,15 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := http.NewServer(cfg.APIPort, logger, cfg.Env)
|
||||
db := ramdb.NewDatabase()
|
||||
err = db.CreateTable("produce", produce.KeyProduceCode)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
produceSvc := produce.NewService(db.From("produce"))
|
||||
|
||||
server := http.NewServer(cfg.APIPort, logger, cfg.Env, produceSvc)
|
||||
|
||||
// Allow app to listen for OS Interrupts and SIGTERMS.
|
||||
serverErrors := make(chan error, 1)
|
||||
|
||||
@@ -16,15 +16,17 @@ type server struct {
|
||||
port int
|
||||
logger *logrus.Logger
|
||||
environment string
|
||||
produceSvc ProduceService
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
// NewServer initializes a new server with the required configurations.
|
||||
func NewServer(port int, logger *logrus.Logger, environment string) *server {
|
||||
func NewServer(port int, logger *logrus.Logger, environment string, produceSvc ProduceService) *server {
|
||||
return &server{
|
||||
port: port,
|
||||
logger: logger,
|
||||
environment: environment,
|
||||
produceSvc: produceSvc,
|
||||
server: &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
ReadTimeout: 60 * time.Second,
|
||||
@@ -54,7 +56,7 @@ func (s *server) buildRoutes() http.Handler {
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
// r.Group(s.produceGroup)
|
||||
s.produceGroup(r)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package http
|
||||
|
||||
import "github.com/davidlick/supermarket-api/internal/produce"
|
||||
|
||||
type ProduceService interface {
|
||||
Add(items []produce.Item) error
|
||||
Remove(item produce.Item) error
|
||||
Get(produceCode string) (item produce.Item, err error)
|
||||
All() (items []produce.Item, err error)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./internal/http/interfaces.go
|
||||
|
||||
// Package http is a generated GoMock package.
|
||||
package http
|
||||
|
||||
import (
|
||||
produce "github.com/davidlick/supermarket-api/internal/produce"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockProduceService is a mock of ProduceService interface
|
||||
type MockProduceService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockProduceServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockProduceServiceMockRecorder is the mock recorder for MockProduceService
|
||||
type MockProduceServiceMockRecorder struct {
|
||||
mock *MockProduceService
|
||||
}
|
||||
|
||||
// NewMockProduceService creates a new mock instance
|
||||
func NewMockProduceService(ctrl *gomock.Controller) *MockProduceService {
|
||||
mock := &MockProduceService{ctrl: ctrl}
|
||||
mock.recorder = &MockProduceServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockProduceService) EXPECT() *MockProduceServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Add mocks base method
|
||||
func (m *MockProduceService) Add(items []produce.Item) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Add", items)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add
|
||||
func (mr *MockProduceServiceMockRecorder) Add(items interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockProduceService)(nil).Add), items)
|
||||
}
|
||||
|
||||
// Remove mocks base method
|
||||
func (m *MockProduceService) Remove(item produce.Item) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Remove", item)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Remove indicates an expected call of Remove
|
||||
func (mr *MockProduceServiceMockRecorder) Remove(item interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockProduceService)(nil).Remove), item)
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockProduceService) Get(produceCode string) (produce.Item, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", produceCode)
|
||||
ret0, _ := ret[0].(produce.Item)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockProduceServiceMockRecorder) Get(produceCode interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockProduceService)(nil).Get), produceCode)
|
||||
}
|
||||
|
||||
// All mocks base method
|
||||
func (m *MockProduceService) All() ([]produce.Item, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "All")
|
||||
ret0, _ := ret[0].([]produce.Item)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// All indicates an expected call of All
|
||||
func (mr *MockProduceServiceMockRecorder) All() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "All", reflect.TypeOf((*MockProduceService)(nil).All))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/davidlick/supermarket-api/internal/produce"
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func (s *server) produceGroup(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Route("/produce", func(r chi.Router) {
|
||||
r.Post("/", s.handleAddProduce)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) handleAddProduce(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
s.writeError(ctx, w, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var items []produce.Item
|
||||
err = json.Unmarshal(body, &items)
|
||||
if err != nil {
|
||||
s.writeError(ctx, w, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.produceSvc.Add(items)
|
||||
if err != nil {
|
||||
s.writeError(ctx, w, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.writeSuccess(ctx, w, nil, http.StatusCreated)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Rhymond/go-money"
|
||||
"github.com/davidlick/supermarket-api/internal/produce"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServer_handleAddProduce(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
body string
|
||||
expectFunc func(mockProduceSvc *MockProduceService)
|
||||
assertFunc func(t *testing.T, w *httptest.ResponseRecorder)
|
||||
}{
|
||||
{
|
||||
test: "it should successfully add valid produce",
|
||||
body: `[{"code":"test","Name":"test","price":{"amount":101,"currency":"USD"}}]`,
|
||||
expectFunc: func(mockProduceSvc *MockProduceService) {
|
||||
mockProduceSvc.EXPECT().Add([]produce.Item{
|
||||
{
|
||||
Code: "test",
|
||||
Name: "test",
|
||||
Price: money.New(101, "USD"),
|
||||
},
|
||||
}).Return(nil)
|
||||
},
|
||||
assertFunc: func(t *testing.T, w *httptest.ResponseRecorder) {
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "it should respond bad request if body isn't an array of produce items",
|
||||
body: ``,
|
||||
expectFunc: func(mockProduceSvc *MockProduceService) {},
|
||||
assertFunc: func(t *testing.T, w *httptest.ResponseRecorder) {
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "it should respond internal server error if adding to service fails",
|
||||
body: `[{"code":"test","Name":"test","price":{"amount":101,"currency":"USD"}}]`,
|
||||
expectFunc: func(mockProduceSvc *MockProduceService) {
|
||||
mockProduceSvc.EXPECT().Add(gomock.Any()).Return(errors.New("test error"))
|
||||
},
|
||||
assertFunc: func(t *testing.T, w *httptest.ResponseRecorder) {
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.test, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/v1/produce", strings.NewReader(tc.body))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
noopLogger := logrus.New()
|
||||
noopLogger.SetOutput(ioutil.Discard)
|
||||
|
||||
mockProduceSvc := NewMockProduceService(ctrl)
|
||||
tc.expectFunc(mockProduceSvc)
|
||||
|
||||
s := NewServer(3000, noopLogger, "test", mockProduceSvc)
|
||||
|
||||
handler := http.HandlerFunc(s.handleAddProduce)
|
||||
handler.ServeHTTP(w, r)
|
||||
|
||||
tc.assertFunc(t, w)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import "github.com/Rhymond/go-money"
|
||||
|
||||
// Item models a produce item.
|
||||
type Item struct {
|
||||
Code string
|
||||
Name string
|
||||
Price *money.Money
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Price *money.Money `json:"price"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user