Implement produce service

This commit is contained in:
2021-06-20 18:16:33 -04:00
parent c7cbd7df57
commit 0136e616fb
14 changed files with 537 additions and 37 deletions
+24
View File
@@ -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)
```
+5
View File
@@ -0,0 +1,5 @@
package produce
const (
KeyProduceCode = "produce_code"
)
+10
View File
@@ -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
}
+77
View File
@@ -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
}
+279
View File
@@ -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)
})
}
}