Implement produce service
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package interfaces
|
||||
|
||||
import "github.com/davidlick/supermarket-api/pkg/ramdb"
|
||||
|
||||
type RamDB interface {
|
||||
Get(column, key string) (r *ramdb.Record, err error)
|
||||
Select(column string) (rr []*ramdb.Record, err error)
|
||||
Insert(r *ramdb.Record) error
|
||||
Delete(r *ramdb.Record) error
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./internal/interfaces/ramdb.go
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
ramdb "github.com/davidlick/supermarket-api/pkg/ramdb"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockRamDB is a mock of RamDB interface
|
||||
type MockRamDB struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRamDBMockRecorder
|
||||
}
|
||||
|
||||
// MockRamDBMockRecorder is the mock recorder for MockRamDB
|
||||
type MockRamDBMockRecorder struct {
|
||||
mock *MockRamDB
|
||||
}
|
||||
|
||||
// NewMockRamDB creates a new mock instance
|
||||
func NewMockRamDB(ctrl *gomock.Controller) *MockRamDB {
|
||||
mock := &MockRamDB{ctrl: ctrl}
|
||||
mock.recorder = &MockRamDBMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockRamDB) EXPECT() *MockRamDBMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockRamDB) Get(column, key string) (*ramdb.Record, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", column, key)
|
||||
ret0, _ := ret[0].(*ramdb.Record)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockRamDBMockRecorder) Get(column, key interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRamDB)(nil).Get), column, key)
|
||||
}
|
||||
|
||||
// Select mocks base method
|
||||
func (m *MockRamDB) Select(column string) ([]*ramdb.Record, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Select", column)
|
||||
ret0, _ := ret[0].([]*ramdb.Record)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Select indicates an expected call of Select
|
||||
func (mr *MockRamDBMockRecorder) Select(column interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockRamDB)(nil).Select), column)
|
||||
}
|
||||
|
||||
// Insert mocks base method
|
||||
func (m *MockRamDB) Insert(r *ramdb.Record) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Insert", r)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Insert indicates an expected call of Insert
|
||||
func (mr *MockRamDBMockRecorder) Insert(r interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockRamDB)(nil).Insert), r)
|
||||
}
|
||||
|
||||
// Delete mocks base method
|
||||
func (m *MockRamDB) Delete(r *ramdb.Record) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", r)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete
|
||||
func (mr *MockRamDBMockRecorder) Delete(r interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRamDB)(nil).Delete), r)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Produce Service
|
||||
|
||||
This produce service allows persisting produce in a database. It supports adding multiple produce items, removing a produce item, getting a produce item by produce code, and selecting all produce items from the database.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
// Create database and table.
|
||||
db := ramdb.NewDatabase()
|
||||
_ = db.CreateTable("produce", KeyProduceCode)
|
||||
|
||||
produceSvc := produce.NewService(db)
|
||||
|
||||
produceItem := produce.Item{
|
||||
Code: "A12T-4GH7-QPL9-3N4M",
|
||||
Name: "Lettuce",
|
||||
Price: money.New(346, "USD"),
|
||||
}
|
||||
|
||||
_ = produceSvc.Add([]Item{produceItem})
|
||||
_, _ = produceSvc.Get(produceItem.Code)
|
||||
_, _ = produceSvc.All()
|
||||
_ = produceSvc.Remove(produceItem)
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
package produce
|
||||
|
||||
const (
|
||||
KeyProduceCode = "produce_code"
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package produce
|
||||
|
||||
import "github.com/Rhymond/go-money"
|
||||
|
||||
// Item models a produce item.
|
||||
type Item struct {
|
||||
Code string
|
||||
Name string
|
||||
Price *money.Money
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package produce
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/davidlick/supermarket-api/internal/interfaces"
|
||||
"github.com/davidlick/supermarket-api/pkg/ramdb"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
db interfaces.RamDB
|
||||
}
|
||||
|
||||
// NewService creates a new produce service for storing produce items.
|
||||
func NewService(db interfaces.RamDB) *service {
|
||||
return &service{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds the Items to the database.
|
||||
func (s *service) Add(items []Item) error {
|
||||
for _, item := range items {
|
||||
rec, err := ramdb.NewRecord(strings.ToLower(item.Code), KeyProduceCode, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.db.Insert(rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes the item from the database.
|
||||
func (s *service) Remove(item Item) error {
|
||||
rec, err := ramdb.NewRecord(strings.ToLower(item.Code), KeyProduceCode, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.db.Delete(rec)
|
||||
}
|
||||
|
||||
// Get fetches the produceCode from the database.
|
||||
func (s *service) Get(produceCode string) (item Item, err error) {
|
||||
rec, err := s.db.Get(KeyProduceCode, strings.ToLower(produceCode))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = rec.Deserialize(&item)
|
||||
return
|
||||
}
|
||||
|
||||
// All returns all produce items stored in the database.
|
||||
func (s *service) All() (items []Item, err error) {
|
||||
recs, err := s.db.Select(KeyProduceCode)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, rec := range recs {
|
||||
var item Item
|
||||
err = rec.Deserialize(&item)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package produce
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Rhymond/go-money"
|
||||
"github.com/davidlick/supermarket-api/internal/mocks"
|
||||
"github.com/davidlick/supermarket-api/pkg/ramdb"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestService_Add(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
expectFunc func(t *testing.T, mockRamDB *mocks.MockRamDB) []Item
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
test: "it should add all Items",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) []Item {
|
||||
items := []Item{
|
||||
{Code: "code-1", Name: "name-1", Price: money.New(101, "USD")},
|
||||
{Code: "code-2", Name: "name-2", Price: money.New(202, "USD")},
|
||||
{Code: "code-3", Name: "name-3", Price: money.New(303, "USD")},
|
||||
{Code: "code-4", Name: "name-4", Price: money.New(404, "USD")},
|
||||
{Code: "code-5", Name: "name-5", Price: money.New(505, "USD")},
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
rec, err := ramdb.NewRecord(item.Code, KeyProduceCode, item)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
mockRamDB.EXPECT().Insert(rec).Return(nil)
|
||||
}
|
||||
|
||||
return items
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "it should return an error from ramdb",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) []Item {
|
||||
mockRamDB.EXPECT().Insert(gomock.Any()).Return(errors.New("test error"))
|
||||
return []Item{
|
||||
{Code: "code-1", Name: "name-1", Price: money.New(101, "USD")},
|
||||
}
|
||||
},
|
||||
expectedError: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.test, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockRamDB := mocks.NewMockRamDB(ctrl)
|
||||
|
||||
items := tc.expectFunc(t, mockRamDB)
|
||||
|
||||
svc := NewService(mockRamDB)
|
||||
err := svc.Add(items)
|
||||
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Add_Lowercased(t *testing.T) {
|
||||
t.Run("it should store produce codes lowercased", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
item := Item{
|
||||
Code: "TEST_CODE",
|
||||
Name: "Name",
|
||||
Price: money.New(101, "USD"),
|
||||
}
|
||||
|
||||
mockRamDB := mocks.NewMockRamDB(ctrl)
|
||||
rec, err := ramdb.NewRecord("test_code", KeyProduceCode, item)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
mockRamDB.EXPECT().Insert(rec).Return(nil)
|
||||
|
||||
svc := NewService(mockRamDB)
|
||||
err = svc.Add([]Item{item})
|
||||
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_Remove(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
expectFunc func(t *testing.T, mockRamDB *mocks.MockRamDB) Item
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
test: "it should successfully remove an item",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) Item {
|
||||
item := Item{Code: "code-1", Name: "name-1", Price: money.New(101, "USD")}
|
||||
rec, err := ramdb.NewRecord(item.Code, KeyProduceCode, item)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
mockRamDB.EXPECT().Delete(rec).Return(nil)
|
||||
return item
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "it should return an error from ramdb",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) Item {
|
||||
mockRamDB.EXPECT().Delete(gomock.Any()).Return(errors.New("test error"))
|
||||
return Item{}
|
||||
},
|
||||
expectedError: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.test, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockRamDB := mocks.NewMockRamDB(ctrl)
|
||||
|
||||
item := tc.expectFunc(t, mockRamDB)
|
||||
|
||||
svc := NewService(mockRamDB)
|
||||
err := svc.Remove(item)
|
||||
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Get(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
expectFunc func(t *testing.T, mockRamDB *mocks.MockRamDB) Item
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
test: "it should return the item successfully",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) Item {
|
||||
item := Item{Code: "produce_code", Name: "name-1", Price: money.New(101, "USD")}
|
||||
rec, err := ramdb.NewRecord(item.Code, KeyProduceCode, item)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
mockRamDB.EXPECT().Get(KeyProduceCode, "test_code").Return(rec, nil)
|
||||
return item
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "it should return an error from ramdb",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) Item {
|
||||
mockRamDB.EXPECT().Get(KeyProduceCode, "test_code").Return(nil, errors.New("test error"))
|
||||
return Item{}
|
||||
},
|
||||
expectedError: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.test, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockRamDB := mocks.NewMockRamDB(ctrl)
|
||||
|
||||
expectedItem := tc.expectFunc(t, mockRamDB)
|
||||
|
||||
svc := NewService(mockRamDB)
|
||||
item, err := svc.Get("test_code")
|
||||
|
||||
assert.Equal(t, expectedItem, item)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Get_CaseInsensitive(t *testing.T) {
|
||||
t.Run("it should lowercase produce codes before getting", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
searchItem := Item{
|
||||
Code: "TEST_CODE",
|
||||
}
|
||||
|
||||
expectedItem := Item{
|
||||
Code: "test_code",
|
||||
}
|
||||
|
||||
rec, err := ramdb.NewRecord("test_code", KeyProduceCode, expectedItem)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
mockRamDB := mocks.NewMockRamDB(ctrl)
|
||||
mockRamDB.EXPECT().Get(KeyProduceCode, "test_code").Return(rec, nil)
|
||||
|
||||
svc := NewService(mockRamDB)
|
||||
|
||||
item, err := svc.Get(searchItem.Code)
|
||||
|
||||
assert.Equal(t, item, expectedItem)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_All(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
expectFunc func(t *testing.T, mockRamDB *mocks.MockRamDB) []Item
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
test: "it should return all items",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) []Item {
|
||||
items := []Item{
|
||||
{Code: "code-1", Name: "name-1", Price: money.New(101, "USD")},
|
||||
{Code: "code-2", Name: "name-2", Price: money.New(202, "USD")},
|
||||
{Code: "code-3", Name: "name-3", Price: money.New(303, "USD")},
|
||||
{Code: "code-4", Name: "name-4", Price: money.New(404, "USD")},
|
||||
{Code: "code-5", Name: "name-5", Price: money.New(505, "USD")},
|
||||
}
|
||||
|
||||
var recs []*ramdb.Record
|
||||
for _, item := range items {
|
||||
rec, err := ramdb.NewRecord(item.Code, KeyProduceCode, item)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
recs = append(recs, rec)
|
||||
}
|
||||
|
||||
mockRamDB.EXPECT().Select(KeyProduceCode).Return(recs, nil)
|
||||
|
||||
return items
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "it should return a ramdb error",
|
||||
expectFunc: func(t *testing.T, mockRamDB *mocks.MockRamDB) []Item {
|
||||
mockRamDB.EXPECT().Select(gomock.Any()).Return(nil, errors.New("test error"))
|
||||
return nil
|
||||
},
|
||||
expectedError: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.test, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
mockRamDB := mocks.NewMockRamDB(ctrl)
|
||||
|
||||
expectedItems := tc.expectFunc(t, mockRamDB)
|
||||
|
||||
svc := NewService(mockRamDB)
|
||||
items, err := svc.All()
|
||||
|
||||
assert.Equal(t, expectedItems, items)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user