mirror of
https://github.com/jimeh/ozu.io.git
synced 2026-02-19 08:06:39 +00:00
Improve shortner tests
This commit is contained in:
94
shortner/mocks/Store.go
Normal file
94
shortner/mocks/Store.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package shortner
|
package shortner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/jimeh/go-base58"
|
"github.com/jimeh/go-base58"
|
||||||
"github.com/jimeh/ozu.io/storage"
|
"github.com/jimeh/ozu.io/storage"
|
||||||
)
|
)
|
||||||
@@ -14,7 +12,6 @@ func New(store storage.Store) *Shortner {
|
|||||||
|
|
||||||
var urlKeyPrefix = []byte("url:")
|
var urlKeyPrefix = []byte("url:")
|
||||||
var uidKeyPrefix = []byte("uid:")
|
var uidKeyPrefix = []byte("uid:")
|
||||||
var errNotFound = errors.New("not found")
|
|
||||||
|
|
||||||
// Shortner interface
|
// Shortner interface
|
||||||
type Shortner struct {
|
type Shortner struct {
|
||||||
@@ -25,7 +22,7 @@ type Shortner struct {
|
|||||||
func (s *Shortner) Shorten(rawURL []byte) (uid []byte, url []byte, err error) {
|
func (s *Shortner) Shorten(rawURL []byte) (uid []byte, url []byte, err error) {
|
||||||
url, err = NormalizeURL(rawURL)
|
url, err = NormalizeURL(rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, []byte{}, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
urlKey := s.makeURLKey(url)
|
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 {
|
if uid != nil && err == nil {
|
||||||
return uid, url, nil
|
return uid, url, nil
|
||||||
} else if err != nil && err.Error() != "not found" {
|
} else if err != nil && err.Error() != "not found" {
|
||||||
return []byte{}, []byte{}, nil
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
uid, err = s.newUID()
|
uid, err = s.newUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, []byte{}, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Store.Set(urlKey, uid)
|
err = s.Store.Set(urlKey, uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, []byte{}, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
uidKey := s.makeUIDKey(uid)
|
uidKey := s.makeUIDKey(uid)
|
||||||
err = s.Store.Set(uidKey, url)
|
err = s.Store.Set(uidKey, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, []byte{}, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return uid, url, nil
|
return uid, url, nil
|
||||||
@@ -62,7 +59,7 @@ func (s *Shortner) Lookup(uid []byte) ([]byte, error) {
|
|||||||
|
|
||||||
url, err := s.Store.Get(uidKey)
|
url, err := s.Store.Get(uidKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return url, nil
|
return url, nil
|
||||||
@@ -71,7 +68,7 @@ func (s *Shortner) Lookup(uid []byte) ([]byte, error) {
|
|||||||
func (s *Shortner) newUID() ([]byte, error) {
|
func (s *Shortner) newUID() ([]byte, error) {
|
||||||
index, err := s.Store.NextSequence()
|
index, err := s.Store.NextSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return base58.Encode(index), nil
|
return base58.Encode(index), nil
|
||||||
|
|||||||
@@ -2,136 +2,135 @@ package shortner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strings"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jimeh/ozu.io/storage"
|
"github.com/jimeh/ozu.io/shortner/mocks"
|
||||||
"github.com/stretchr/testify/suite"
|
"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
|
// Mocks
|
||||||
|
|
||||||
func NewMockStore() *MockStore {
|
//go:generate mockery -name Store -dir .. -recursive
|
||||||
return &MockStore{
|
|
||||||
Data: map[string][]byte{},
|
|
||||||
Sequence: 1000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockStore struct {
|
// Suite Setup
|
||||||
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
|
|
||||||
|
|
||||||
type ShortnerSuite struct {
|
type ShortnerSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
shortner *Shortner
|
store *mocks.Store
|
||||||
store storage.Store
|
shortner *Shortner
|
||||||
|
errNotFound error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShortnerSuite) SetupTest() {
|
func (s *ShortnerSuite) SetupTest() {
|
||||||
s.store = NewMockStore()
|
s.store = new(mocks.Store)
|
||||||
s.shortner = New(s.store)
|
s.shortner = New(s.store)
|
||||||
}
|
s.errNotFound = errors.New("not found")
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
|
|
||||||
func (s *ShortnerSuite) TestShorten() {
|
func (s *ShortnerSuite) TestShortenExisting() {
|
||||||
for _, e := range shortenExamples {
|
rawURL := []byte("http://google.com/")
|
||||||
uid, url, err := s.shortner.Shorten([]byte(e.url))
|
uid := []byte("ig")
|
||||||
s.Equal(nil, err)
|
|
||||||
s.Equal([]byte(e.uid), uid)
|
s.store.On("Get", append([]byte("url:"), rawURL...)).Return(uid, nil)
|
||||||
if e.normalized != "" {
|
|
||||||
s.Equal([]byte(e.normalized), url)
|
resultUID, resultURL, err := s.shortner.Shorten(rawURL)
|
||||||
} else {
|
s.NoError(err)
|
||||||
s.Equal([]byte(e.url), url)
|
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: "*$)]+_<?)",
|
||||||
|
error: "invalid URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "",
|
||||||
|
error: "invalid URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "file:///bin/bash",
|
||||||
|
error: "schema 'file://' not allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/users/view.php?uid=138495",
|
||||||
|
error: "invalid URL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "http://long.com/" + strings.Repeat("0", 3000),
|
||||||
|
error: "invalid URL",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range examples {
|
||||||
|
rUID, rURL, err := s.shortner.Shorten([]byte(e.url))
|
||||||
|
s.Nil(rUID)
|
||||||
|
s.Nil(rURL)
|
||||||
|
s.EqualError(err, e.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShortnerSuite) TestLookup() {
|
func (s *ShortnerSuite) TestShortenStoreError() {
|
||||||
s.Seed()
|
url := []byte("https://google.com/")
|
||||||
|
storeErr := errors.New("leveldb: something wrong")
|
||||||
|
|
||||||
for _, e := range shortenExamples {
|
s.store.On("Get", append([]byte("url:"), url...)).Return(nil, storeErr)
|
||||||
url, err := s.shortner.Lookup([]byte(e.uid))
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
if e.normalized != "" {
|
rUID, rURL, err := s.shortner.Shorten(url)
|
||||||
s.Equal([]byte(e.normalized), url)
|
s.Nil(rUID)
|
||||||
} else {
|
s.Nil(rURL)
|
||||||
s.Equal([]byte(e.url), url)
|
s.EqualError(err, storeErr.Error())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
func (s *ShortnerSuite) TestLookupExisting() {
|
||||||
|
url := []byte("https://google.com/")
|
||||||
|
uid := []byte("ig")
|
||||||
|
|
||||||
|
s.store.On("Get", append([]byte("uid:"), uid...)).Return(url, nil)
|
||||||
|
|
||||||
|
rURL, err := s.shortner.Lookup(uid)
|
||||||
|
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(url, rURL)
|
||||||
|
s.store.AssertExpectations(s.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShortnerSuite) TestLookupNonExistant() {
|
||||||
|
uid := []byte("ig")
|
||||||
|
|
||||||
|
s.store.On("Get", append([]byte("uid:"), uid...)).Return(nil, s.errNotFound)
|
||||||
|
|
||||||
|
rURL, err := s.shortner.Lookup(uid)
|
||||||
|
|
||||||
|
s.EqualError(err, "not found")
|
||||||
|
s.Nil(rURL)
|
||||||
|
s.store.AssertExpectations(s.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run Suite
|
// Run Suite
|
||||||
@@ -139,24 +138,3 @@ func (s *ShortnerSuite) TestLookup() {
|
|||||||
func TestShortnerSuite(t *testing.T) {
|
func TestShortnerSuite(t *testing.T) {
|
||||||
suite.Run(t, new(ShortnerSuite))
|
suite.Run(t, new(ShortnerSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmarks
|
|
||||||
|
|
||||||
func BenchmarkShorten(b *testing.B) {
|
|
||||||
shortner := New(NewMockStore())
|
|
||||||
rawURL := []byte("https://google.com/")
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
_, _, _ = shortner.Shorten(append(rawURL, strconv.Itoa(n)...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLookup(b *testing.B) {
|
|
||||||
shortner := New(NewMockStore())
|
|
||||||
rawURL := []byte("https://google.com/")
|
|
||||||
uid, _, _ := shortner.Shorten(rawURL)
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
_, _ = shortner.Lookup(uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user