Skip to content

Commit

Permalink
Implement Error type
Browse files Browse the repository at this point in the history
WIP

WIP

Implement tests for invalid value errors and pass them

Update code with final version of lab06 improvements
  • Loading branch information
runnerdave committed Jul 14, 2019
1 parent 33e5334 commit 4c5f9a9
Show file tree
Hide file tree
Showing 7 changed files with 429 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) (bool, error) {
if value < 0 {
return false, Errorf(ErrInvalidValue, "Puppy has invalid value (%f)", value)
}
return true, 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, error := mapStore.ReadPuppy(12)
fmt.Fprint(out, error)
}
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()")
}
}
54 changes: 54 additions & 0 deletions 07_errors/runnerdave/map_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 {
var m MapStore
m.puppies = make(map[uint16]Puppy)
return &m
}

// CreatePuppy saves new puppy if not in store, if it is already returns error
func (m *MapStore) CreatePuppy(p *Puppy) error {
if isValid, invalidValueError := validateValue(p.Value); !isValid {
return invalidValueError
}
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 isValid, invalidValueError := validateValue(p.Value); !isValid {
return invalidValueError
}
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) (bool, error) {
if _, ok := m.puppies[id]; ok {
delete(m.puppies, id)
return true, nil
}
return false, Errorf(ErrIDNotFound, "Puppy with ID:%d not found", id)
}
206 changes: 206 additions & 0 deletions 07_errors/runnerdave/storer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
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
}

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

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

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

func (s *storerSuite) TestUpdatePuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
targetPuppy := puppy2()
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, &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")

// cleanup
_, deleteError := s.store.DeletePuppy(11)
s.T().Log(deleteError)
}

func (s *storerSuite) TestReadPuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
createError := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(createError, "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")

// cleanup
_, deleteError := s.store.DeletePuppy(11)
s.T().Log(deleteError)
}

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 error when puppy is not found")
serr, ok := err.(*Error)
if ok {
assert.Equal(uint16(0x2), serr.Code)
}
}

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

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

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

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

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

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

func (s *storerSuite) TestCreateExistingPuppy() {
// given
assert := tassert.New(s.T())
testPuppy := puppy1()
createError := s.store.CreatePuppy(&testPuppy)
r := require.New(s.T())
r.NoError(createError, "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")

// cleanup
_, deleteError := s.store.DeletePuppy(11)
s.T().Log(deleteError)
}

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)
if 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)
if ok {
assert.Equal(uint16(0x1), serr.Code)
}
}

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

0 comments on commit 4c5f9a9

Please sign in to comment.