Skip to content

Commit

Permalink
Merge pull request #557 from runnerdave/puppy_store_error_handling
Browse files Browse the repository at this point in the history
Puppy store error handling
  • Loading branch information
runnerdave authored Jul 18, 2019
2 parents 447d836 + 7e28d16 commit 0bae47f
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 0 deletions.
35 changes: 35 additions & 0 deletions 07_errors/runnerdave/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import "fmt"

// Error codes
const (
// ErrUnknown is used when an unknown error occurred
ErrUnknown uint16 = iota
// ErrInvalidValue is used when the value for the puppy is negative
ErrInvalidValue
// ErrIDNotFound is used when attempting to read a non-existing entry
ErrIDNotFound
)

// Error struct to identify errors in Puppy store
type Error struct {
Code uint16 `json:"code"`
Message string `json:"message"`
}

func (e *Error) Error() string {
return fmt.Sprintf("%d: %s", e.Code, e.Message)
}

// Errorf creates a new Error with formatting
func Errorf(code uint16, format string, args ...interface{}) *Error {
return &Error{code, fmt.Sprintf(format, args...)}
}

func validateValue(value float32) error {
if value < 0 {
return Errorf(ErrInvalidValue, "puppy has invalid value (%f)", value)
}
return nil
}
30 changes: 30 additions & 0 deletions 07_errors/runnerdave/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"fmt"
"io"
"os"
)

var out io.Writer = os.Stdout

func main() {
puppy1 := Puppy{ID: 11, Breed: "Chihuahua", Colour: "Brown", Value: 12.30}
puppy2 := Puppy{ID: 11, Breed: "Chihuahua", Colour: "Brown", Value: 10.30}

mapStore := NewMapStore()
mapCreateErr := mapStore.CreatePuppy(&puppy1)

puppyMap, _ := mapStore.ReadPuppy(11)
fmt.Fprintf(out, "Puppy created in map of Breed:%s, errors at creation:%v\n", puppyMap.Breed, mapCreateErr)

syncStore := NewSyncStore()
syncCreateErr := syncStore.CreatePuppy(&puppy1)
syncUpdateErr := syncStore.UpdatePuppy(11, &puppy2)
puppySync, _ := syncStore.ReadPuppy(11)
fmt.Fprintf(out, "Puppy created in sync of Breed:%s, value updated to:%f, error at creation:%v, error in update:%v\n",
puppySync.Breed, puppySync.Value, syncCreateErr, syncUpdateErr)

puppyMap, err := mapStore.ReadPuppy(12)
fmt.Fprint(out, err)
}
25 changes: 25 additions & 0 deletions 07_errors/runnerdave/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"bytes"
"strconv"
"testing"
)

func TestMainOutput(t *testing.T) {
var buf bytes.Buffer
out = &buf

main()

expected := strconv.Quote(`Puppy created in map of Breed:Chihuahua, errors at creation:<nil>
Puppy created in sync of Breed:Chihuahua, value updated to:10.300000, error at creation:<nil>, error in update:<nil>
2: puppy with ID:12 not found`)
actual := strconv.Quote(buf.String())
t.Logf("expected:%s", expected)
t.Logf("actual:%s", actual)

if expected != actual {
t.Errorf("Unexpected output in main()")
}
}
52 changes: 52 additions & 0 deletions 07_errors/runnerdave/map_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

// MapStore represents a simple map storage for the Puppy store
type MapStore struct {
puppies map[uint16]Puppy
}

// NewMapStore creates a new in-memory store with map intialised
func NewMapStore() *MapStore {
return &MapStore{puppies: map[uint16]Puppy{}}
}

// CreatePuppy saves new puppy if not in store, if it is already returns error
func (m *MapStore) CreatePuppy(p *Puppy) error {
if err := validateValue(p.Value); err != nil {
return err
}
if _, ok := m.puppies[p.ID]; ok {
return Errorf(ErrUnknown, "puppy with id %d already exists", p.ID)
}
m.puppies[p.ID] = *p
return nil
}

// ReadPuppy reads store by Puppy ID
func (m *MapStore) ReadPuppy(id uint16) (Puppy, error) {
if puppy, ok := m.puppies[id]; ok {
return puppy, nil
}
return Puppy{}, Errorf(ErrIDNotFound, "puppy with ID:%d not found", id)
}

// UpdatePuppy updates puppy with new value if ID present otherwise error
func (m *MapStore) UpdatePuppy(id uint16, p *Puppy) error {
if err := validateValue(p.Value); err != nil {
return err
}
if _, ok := m.puppies[id]; !ok {
return Errorf(ErrIDNotFound, "puppy with ID:%d not found", id)
}
m.puppies[id] = *p
return nil
}

// DeletePuppy deletes a puppy by id from the store
func (m *MapStore) DeletePuppy(id uint16) error {
if _, ok := m.puppies[id]; ok {
delete(m.puppies, id)
return nil
}
return Errorf(ErrIDNotFound, "puppy with ID:%d not found", id)
}
193 changes: 193 additions & 0 deletions 07_errors/runnerdave/storer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package main

import (
"testing"

tassert "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

var (
puppy1 = func() Puppy {
return Puppy{ID: 11, Breed: "Chihuahua", Colour: "Brown", Value: 12.30}
}
puppy2 = func() Puppy {
return Puppy{ID: 12, Breed: "Cacri", Colour: "Undefined", Value: 1.30}
}
puppy3 = func() Puppy {
return Puppy{ID: 12, Breed: "Imaginary", Colour: "Undefined", Value: -1.30}
}
)

type storerSuite struct {
suite.Suite
store Storer
storerType func() Storer
}

func (s *storerSuite) TestUpdatePuppyIDDoesNotExist() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()

// when
err := s.store.UpdatePuppy(13, &testPuppy)

// then
assert.Error(err, "Should produce an error if id not found")
serr, ok := err.(*Error)
assert.True(ok)
assert.Equal(uint16(0x2), serr.Code)
}

func (s *storerSuite) TestUpdatePuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
targetPuppy := puppy2()
cerr := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(cerr, "Create should not produce an error")

// when
uerr := s.store.UpdatePuppy(11, &targetPuppy)

// then
r.NoError(uerr, "Should be able to update store")
updatedPuppy, err := s.store.ReadPuppy(11)
r.NoError(err, "Should be able to read updated puppy")
assert.Equal(targetPuppy, updatedPuppy, "Updated puppy should be equal to puppy2")
}

func (s *storerSuite) TestReadPuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
cerr := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(cerr, "Create should not produce an error")

// when
newPuppy, err := s.store.ReadPuppy(11)

// then
r.NoError(err, "Should be able to read a newly added puppy")
assert.Equal(testPuppy, newPuppy, "Newly added puppy should be equal to test puppy")
}

func (s *storerSuite) TestReadNonExistingPuppy() {
// given
assert := tassert.New(s.T())
r := require.New(s.T())

// when
_, err := s.store.ReadPuppy(12)

// then
r.Error(err, "Should produce an error when puppy is not found")
serr, ok := err.(*Error)
assert.True(ok)
assert.Equal(uint16(0x2), serr.Code)
}

func (s *storerSuite) TestDeletePuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
cerr := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(cerr, "Create should not produce an error")

// when
err := s.store.DeletePuppy(11)

// then
r.NoError(err, "Should be able to delete a newly added puppy")
_, rerr := s.store.ReadPuppy(11)
serr, ok := rerr.(*Error)
assert.True(ok)
assert.Equal(uint16(0x2), serr.Code)
}

func (s *storerSuite) TestDeleteNonExistingPuppy() {
// given
assert := tassert.New(s.T())
r := require.New(s.T())

// when
err := s.store.DeletePuppy(11)

// then
r.Error(err, "Should not be able to delete a non existing puppy")
serr, ok := err.(*Error)
assert.True(ok)
assert.Equal(uint16(0x2), serr.Code)
}

func (s *storerSuite) TestCreateExistingPuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
cerr := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(cerr, "Create should not produce an error")

// when
err := s.store.CreatePuppy(&testPuppy)

// then
assert.Error(err, "Should not be able to create twice a the same puppy")
}

func (s *storerSuite) TestCreatePuppyWithInvalidValue() {
// given
assert := tassert.New(s.T())
testPuppy := puppy3()
r := require.New(s.T())

// when
createError := s.store.CreatePuppy(&testPuppy)

// then
r.Error(createError, "Should not allow to create a puppy with invalid value")
serr, ok := createError.(*Error)
assert.True(ok)
assert.Equal(uint16(0x1), serr.Code)
}

func (s *storerSuite) TestUpdatePuppyWithInvalidValue() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
updatePuppy := puppy3()
createError := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(createError, "Create should not produce an error")

// when
uerr := s.store.UpdatePuppy(11, &updatePuppy)

// then
r.Error(uerr, "Should not allow to update a puppy with invalid value")
serr, ok := uerr.(*Error)
assert.True(ok)
assert.Equal(uint16(0x1), serr.Code)
}

func (s *storerSuite) SetupTest() {
s.store = s.storerType()
}

func TestStorer(t *testing.T) {
syncSuite := storerSuite{
store: NewSyncStore(),
storerType: func() Storer { return NewSyncStore() },
}
mapSuite := storerSuite{
store: NewMapStore(),
storerType: func() Storer { return NewMapStore() },
}
suite.Run(t, &syncSuite)
suite.Run(t, &mapSuite)
}
Loading

0 comments on commit 0bae47f

Please sign in to comment.