From df1e8fff45fa1786c4d4f488cc42894402cf2c4a Mon Sep 17 00:00:00 2001 From: Siyuan Hu Date: Sat, 10 Aug 2019 18:07:09 +1000 Subject: [PATCH] Create CRUD puppy solution with map and syncMap --- 06_puppy/hsy3418/main.go | 26 +++++++ 06_puppy/hsy3418/main_test.go | 18 +++++ 06_puppy/hsy3418/mapStore.go | 49 ++++++++++++++ 06_puppy/hsy3418/store_test.go | 120 +++++++++++++++++++++++++++++++++ 06_puppy/hsy3418/syncStore.go | 57 ++++++++++++++++ 06_puppy/hsy3418/types.go | 17 +++++ 6 files changed, 287 insertions(+) create mode 100644 06_puppy/hsy3418/main.go create mode 100644 06_puppy/hsy3418/main_test.go create mode 100644 06_puppy/hsy3418/mapStore.go create mode 100644 06_puppy/hsy3418/store_test.go create mode 100644 06_puppy/hsy3418/syncStore.go create mode 100644 06_puppy/hsy3418/types.go diff --git a/06_puppy/hsy3418/main.go b/06_puppy/hsy3418/main.go new file mode 100644 index 000000000..820427f1f --- /dev/null +++ b/06_puppy/hsy3418/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "io" + "os" +) + +var out io.Writer = os.Stdout + +func main() { + p1 := Puppy{ID: 101, Breed: "Poodle", Colour: "White", Value: 1280.5} + p2 := Puppy{ID: 102, Breed: "Poodle", Colour: "Grey", Value: 1340.5} + + mapStore := NewMapStore() + syncStore := NewSyncStore() + + _ = mapStore.CreatePuppy(p1) + _ = syncStore.CreatePuppy(p2) + + puppy, _ := mapStore.ReadPuppy(101) + puppy2, _ := syncStore.ReadPuppy(102) + fmt.Fprintf(out, "Puppy ID %d is %v", puppy.ID, puppy.Value) + fmt.Fprintf(out, "Puppy ID %d is %v", puppy2.ID, puppy2.Value) + +} diff --git a/06_puppy/hsy3418/main_test.go b/06_puppy/hsy3418/main_test.go new file mode 100644 index 000000000..95aeb49ae --- /dev/null +++ b/06_puppy/hsy3418/main_test.go @@ -0,0 +1,18 @@ +package main + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMain(t *testing.T) { + assert := assert.New(t) + var buf bytes.Buffer + out = &buf + main() + expected := "Puppy ID 101 is 1280.5Puppy ID 102 is 1340.5" + actual := buf.String() + assert.Equal(expected, actual) +} diff --git a/06_puppy/hsy3418/mapStore.go b/06_puppy/hsy3418/mapStore.go new file mode 100644 index 000000000..0c7050294 --- /dev/null +++ b/06_puppy/hsy3418/mapStore.go @@ -0,0 +1,49 @@ +package main + +import "fmt" + +// MapStore is a implementation of storer for the storage of puppies +type MapStore struct { + puppies map[int32]Puppy +} + +func NewMapStore() *MapStore { + return &MapStore{ + puppies: map[int32]Puppy{}, + } +} + +// CreatePuppy adds a nuw puppy to the puppies store +func (m *MapStore) CreatePuppy(puppy Puppy) error { + if _, exists := m.puppies[puppy.ID]; !exists { + m.puppies[puppy.ID] = puppy + return nil + } + return fmt.Errorf("puppy with %d ID already exists", puppy.ID) +} + +// ReadPuppy retrieves the puppy for a given id from puppies store +func (m *MapStore) ReadPuppy(id int32) (Puppy, error) { + if _, exists := m.puppies[id]; !exists { + return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id) + } + return m.puppies[id], nil +} + +//UpdatePuppy updates the puppy for the given id +func (m *MapStore) UpdatePuppy(puppy Puppy) error { + if _, exists := m.puppies[puppy.ID]; !exists { + return fmt.Errorf("puppy with %d ID does not exist", puppy.ID) + } + m.puppies[puppy.ID] = puppy + return nil +} + +//DeletePuppy delete the puppy for the given id from puppies store +func (m *MapStore) DeletePuppy(id int32) error { + if _, exists := m.puppies[id]; exists { + delete(m.puppies, id) + return nil + } + return fmt.Errorf("puppy with %d ID does not exist", id) +} diff --git a/06_puppy/hsy3418/store_test.go b/06_puppy/hsy3418/store_test.go new file mode 100644 index 000000000..ddc583244 --- /dev/null +++ b/06_puppy/hsy3418/store_test.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +func (suite *storerTestSuite) SetupTest() { + suite.store = suite.makeStore() + suite.toBeCreatedPuppy = Puppy{ID: 101, Breed: "Poodle", Colour: "White", Value: 1000.5} + suite.existsPuppy = Puppy{ID: 102, Breed: "Poodle", Colour: "White", Value: 1280.5} + suite.toBeUpdatedPuppy = Puppy{ID: 102, Breed: "Poodle", Colour: "White", Value: 2000} + err := suite.store.CreatePuppy(suite.existsPuppy) + if err != nil { + suite.FailNow("Failed to setup test") + } +} + +type storerTestSuite struct { + suite.Suite + store Storer + makeStore func() Storer + toBeCreatedPuppy Puppy + existsPuppy Puppy + toBeUpdatedPuppy Puppy +} + +func (suite *storerTestSuite) TestCreatePuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + input Puppy + expected error + }{ + {"Create new puppy", suite.toBeCreatedPuppy, nil}, + {"Create existing puppy", suite.toBeCreatedPuppy, fmt.Errorf("puppy with 101 ID already exists")}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + err := suite.store.CreatePuppy(tc.input) + assert.Equal(tc.expected, err) + }) + } + +} + +func (suite *storerTestSuite) TestUpdatePuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + inputPuppy Puppy + expectedError error + }{ + {"Update puppy successfully", suite.toBeUpdatedPuppy, nil}, + {"Update non-existing puppy", Puppy{}, fmt.Errorf("puppy with 0 ID does not exist")}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + err := suite.store.UpdatePuppy(tc.inputPuppy) + assert.Equal(tc.expectedError, err) + }) + } + +} + +func (suite *storerTestSuite) TestReadPuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + input int32 + expected Puppy + expectedError error + }{ + {"Read puppy successfully", suite.existsPuppy.ID, suite.existsPuppy, nil}, + {"Read non-existing puppy", suite.toBeCreatedPuppy.ID, Puppy{}, fmt.Errorf("puppy with 101 ID does not exist")}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + readPuppy, err := suite.store.ReadPuppy(tc.input) + assert.Equal(tc.expected, readPuppy) + assert.Equal(tc.expectedError, err) + }) + } + +} + +func (suite *storerTestSuite) TestDeletePuppy() { + assert := assert.New(suite.T()) + testCases := []struct { + title string + input int32 + expectedError error + }{ + {"Delete puppy successfully", suite.existsPuppy.ID, nil}, + {"Delete non-existing puppy", suite.toBeCreatedPuppy.ID, fmt.Errorf("puppy with 101 ID does not exist")}, + } + for _, tc := range testCases { + tc := tc + suite.T().Run(tc.title, func(t *testing.T) { + err := suite.store.DeletePuppy(tc.input) + assert.Equal(tc.expectedError, err) + }) + } + +} + +func TestStorers(t *testing.T) { + suite.Run(t, &storerTestSuite{ + makeStore: func() Storer { return NewMapStore() }, + }) + suite.Run(t, &storerTestSuite{ + makeStore: func() Storer { return NewSyncStore() }, + }) +} diff --git a/06_puppy/hsy3418/syncStore.go b/06_puppy/hsy3418/syncStore.go new file mode 100644 index 000000000..b2d441dd4 --- /dev/null +++ b/06_puppy/hsy3418/syncStore.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "sync" +) + +type SyncStore struct { + sync.Mutex + sync.Map +} + +func NewSyncStore() *SyncStore { + return &SyncStore{} +} + +// CreatePuppy adds a nuw puppy to the puppies store +func (m *SyncStore) CreatePuppy(puppy Puppy) error { + m.Lock() + defer m.Unlock() + if _, exists := m.Load(puppy.ID); exists { + return fmt.Errorf("puppy with %d ID already exists", puppy.ID) + } + m.Store(puppy.ID, puppy) + return nil +} + +// ReadPuppy retrieves the puppy for a given id from puppies store +func (m *SyncStore) ReadPuppy(id int32) (Puppy, error) { + if p, exists := m.Load(id); exists { + puppy, _ := p.(Puppy) + return puppy, nil + } + return Puppy{}, fmt.Errorf("puppy with %d ID does not exist", id) +} + +//UpdatePuppy updates the puppy for the given id +func (m *SyncStore) UpdatePuppy(puppy Puppy) error { + m.Lock() + defer m.Unlock() + if _, exists := m.Load(puppy.ID); !exists { + return fmt.Errorf("puppy with %d ID does not exist", puppy.ID) + } + m.Store(puppy.ID, puppy) + return nil +} + +//DeletePuppy delete the puppy for the given id from puppies store +func (m *SyncStore) DeletePuppy(id int32) error { + m.Lock() + defer m.Unlock() + if _, exists := m.Load(id); exists { + m.Delete(id) + return nil + } + return fmt.Errorf("puppy with %d ID does not exist", id) +} diff --git a/06_puppy/hsy3418/types.go b/06_puppy/hsy3418/types.go new file mode 100644 index 000000000..fa6bf05d9 --- /dev/null +++ b/06_puppy/hsy3418/types.go @@ -0,0 +1,17 @@ +package main + +// Puppy defines the data structure corresponding to a pet +type Puppy struct { + ID int32 `json:"id"` + Value float32 `json:"value"` + Breed string `json:"breed"` + Colour string `json:"colour"` +} + +//Storer define standard CRUD operations for puppys +type Storer interface { + CreatePuppy(Puppy) error + ReadPuppy(ID int32) (Puppy, error) + UpdatePuppy(puppy Puppy) error + DeletePuppy(ID int32) error +}