From ae48e665f0b6ac2a01c8c91cbd74566e118c98df Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sun, 10 Jul 2016 16:33:46 +0100 Subject: [PATCH] Improve shortner tests --- shortner/mocks/Store.go | 94 +++++++++++++++ shortner/shortner.go | 17 ++- shortner/shortner_test.go | 232 +++++++++++++++++--------------------- 3 files changed, 206 insertions(+), 137 deletions(-) create mode 100644 shortner/mocks/Store.go diff --git a/shortner/mocks/Store.go b/shortner/mocks/Store.go new file mode 100644 index 0000000..3f9cacb --- /dev/null +++ b/shortner/mocks/Store.go @@ -0,0 +1,94 @@ +package mocks + +import "github.com/stretchr/testify/mock" + +// Store is an autogenerated mock type for the Store type +type Store struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *Store) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Delete provides a mock function with given fields: _a0 +func (_m *Store) Delete(_a0 []byte) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func([]byte) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Get provides a mock function with given fields: _a0 +func (_m *Store) Get(_a0 []byte) ([]byte, error) { + ret := _m.Called(_a0) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NextSequence provides a mock function with given fields: +func (_m *Store) NextSequence() (int, error) { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Set provides a mock function with given fields: _a0, _a1 +func (_m *Store) Set(_a0 []byte, _a1 []byte) error { + ret := _m.Called(_a0, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func([]byte, []byte) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/shortner/shortner.go b/shortner/shortner.go index 6af1e76..d2a91c4 100644 --- a/shortner/shortner.go +++ b/shortner/shortner.go @@ -1,8 +1,6 @@ package shortner import ( - "errors" - "github.com/jimeh/go-base58" "github.com/jimeh/ozu.io/storage" ) @@ -14,7 +12,6 @@ func New(store storage.Store) *Shortner { var urlKeyPrefix = []byte("url:") var uidKeyPrefix = []byte("uid:") -var errNotFound = errors.New("not found") // Shortner interface type Shortner struct { @@ -25,7 +22,7 @@ type Shortner struct { func (s *Shortner) Shorten(rawURL []byte) (uid []byte, url []byte, err error) { url, err = NormalizeURL(rawURL) if err != nil { - return []byte{}, []byte{}, err + return nil, nil, err } urlKey := s.makeURLKey(url) @@ -34,23 +31,23 @@ func (s *Shortner) Shorten(rawURL []byte) (uid []byte, url []byte, err error) { if uid != nil && err == nil { return uid, url, nil } else if err != nil && err.Error() != "not found" { - return []byte{}, []byte{}, nil + return nil, nil, err } uid, err = s.newUID() if err != nil { - return []byte{}, []byte{}, err + return nil, nil, err } err = s.Store.Set(urlKey, uid) if err != nil { - return []byte{}, []byte{}, err + return nil, nil, err } uidKey := s.makeUIDKey(uid) err = s.Store.Set(uidKey, url) if err != nil { - return []byte{}, []byte{}, err + return nil, nil, err } return uid, url, nil @@ -62,7 +59,7 @@ func (s *Shortner) Lookup(uid []byte) ([]byte, error) { url, err := s.Store.Get(uidKey) if err != nil { - return []byte{}, err + return nil, err } return url, nil @@ -71,7 +68,7 @@ func (s *Shortner) Lookup(uid []byte) ([]byte, error) { func (s *Shortner) newUID() ([]byte, error) { index, err := s.Store.NextSequence() if err != nil { - return []byte{}, err + return nil, err } return base58.Encode(index), nil diff --git a/shortner/shortner_test.go b/shortner/shortner_test.go index a8c1db9..ffe31dc 100644 --- a/shortner/shortner_test.go +++ b/shortner/shortner_test.go @@ -2,136 +2,135 @@ package shortner import ( "errors" - "strconv" - "sync" + "strings" "testing" - "github.com/jimeh/ozu.io/storage" + "github.com/jimeh/ozu.io/shortner/mocks" "github.com/stretchr/testify/suite" ) -// Test Cases - -var shortenExamples = []struct { - uid string - url string - normalized string -}{ - {uid: "ig", url: "google.com", normalized: "http://google.com/"}, - {uid: "ih", url: "https://google.com", normalized: "https://google.com/"}, - {uid: "ig", url: "http://google.com", normalized: "http://google.com/"}, - {uid: "ih", url: "https://google.com/"}, - {uid: "ig", url: "google.com/", normalized: "http://google.com/"}, - {uid: "ii", url: "https://github.com/"}, - {uid: "ij", url: "https://gist.github.com/"}, -} - // Mocks -func NewMockStore() *MockStore { - return &MockStore{ - Data: map[string][]byte{}, - Sequence: 1000, - } -} +//go:generate mockery -name Store -dir .. -recursive -type MockStore struct { - sync.RWMutex - Data map[string][]byte - Sequence int -} - -func (s *MockStore) Close() error { - return nil -} - -func (s *MockStore) Get(key []byte) ([]byte, error) { - s.RLock() - defer s.RUnlock() - value := s.Data[string(key)] - if value == nil { - return nil, errors.New("not found") - } - return value, nil -} - -func (s *MockStore) Set(key []byte, value []byte) error { - s.Lock() - defer s.Unlock() - s.Data[string(key)] = value - return nil -} - -func (s *MockStore) Delete(key []byte) error { - s.Lock() - defer s.Unlock() - delete(s.Data, string(key)) - return nil -} - -func (s *MockStore) NextSequence() (int, error) { - s.Lock() - defer s.Unlock() - s.Sequence++ - return s.Sequence, nil -} - -// Setup Suite +// Suite Setup type ShortnerSuite struct { suite.Suite - shortner *Shortner - store storage.Store + store *mocks.Store + shortner *Shortner + errNotFound error } func (s *ShortnerSuite) SetupTest() { - s.store = NewMockStore() + s.store = new(mocks.Store) s.shortner = New(s.store) -} - -func (s *ShortnerSuite) Seed() { - r := s.Require() - - for _, e := range shortenExamples { - uid, url, err := s.shortner.Shorten([]byte(e.url)) - r.Equal([]byte(e.uid), uid) - if e.normalized != "" { - r.Equal([]byte(e.normalized), url) - } else { - r.Equal([]byte(e.url), url) - } - r.NoError(err) - } + s.errNotFound = errors.New("not found") } // Tests -func (s *ShortnerSuite) TestShorten() { - for _, e := range shortenExamples { - uid, url, err := s.shortner.Shorten([]byte(e.url)) - s.Equal(nil, err) - s.Equal([]byte(e.uid), uid) - if e.normalized != "" { - s.Equal([]byte(e.normalized), url) - } else { - s.Equal([]byte(e.url), url) - } +func (s *ShortnerSuite) TestShortenExisting() { + rawURL := []byte("http://google.com/") + uid := []byte("ig") + + s.store.On("Get", append([]byte("url:"), rawURL...)).Return(uid, nil) + + resultUID, resultURL, err := s.shortner.Shorten(rawURL) + s.NoError(err) + s.Equal(uid, resultUID) + s.Equal(rawURL, resultURL) + s.store.AssertExpectations(s.T()) +} + +func (s *ShortnerSuite) TestShortenNew() { + rawURL := []byte("https://google.com") + url := []byte("https://google.com/") + uid := []byte("ig") + + s.store.On("Get", append([]byte("url:"), url...)).Return(nil, s.errNotFound) + s.store.On("NextSequence").Return(1001, nil) + s.store.On("Set", append([]byte("url:"), url...), uid).Return(nil) + s.store.On("Set", append([]byte("uid:"), uid...), url).Return(nil) + + rUID, rURL, err := s.shortner.Shorten(rawURL) + + s.NoError(err) + s.Equal(uid, rUID) + s.Equal(url, rURL) + s.store.AssertExpectations(s.T()) +} + +func (s *ShortnerSuite) TestShortenInvalidURL() { + examples := []struct { + url string + error string + }{ + { + url: "*$)]+_