-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Kasun
authored and
Kasun
committed
Aug 9, 2019
1 parent
2e532bd
commit 7d37597
Showing
9 changed files
with
493 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package main | ||
|
||
import "fmt" | ||
|
||
const ( | ||
ErrInvalidVal = 400 | ||
ErrNotFound = 404 | ||
ErrInternal = 500 | ||
ErrOpenDB = 501 | ||
) | ||
|
||
var ErrStr = map[int]string{ | ||
ErrInvalidVal: "value cannot be negative", | ||
ErrNotFound: "puppy with id=%v not found", | ||
ErrInternal: "internal system error", | ||
ErrOpenDB: "error occurred while opening db %v", | ||
} | ||
|
||
func NewError(code int, args ...interface{}) *Error { | ||
return &Error{Code: code, Message: fmt.Sprintf(ErrStr[code], args...)} | ||
} | ||
|
||
func (e *Error) Error() string { | ||
return fmt.Sprintf("ERROR %v:%v", e.Code, e.Message) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"log" | ||
"strconv" | ||
|
||
"github.com/sirupsen/logrus" | ||
"github.com/syndtr/goleveldb/leveldb" | ||
) | ||
|
||
func NewLeveldbStore(path, dataFileName string) (*LeveldbStore, error) { | ||
store := &LeveldbStore{ | ||
count: 0, | ||
path: path, | ||
dataFileName: dataFileName, | ||
indexFileName: "index.db", indexName: "last_index"} | ||
|
||
db, err := openDB(store.path+"/"+store.indexFileName, true) | ||
if err != nil { | ||
return store, err | ||
} | ||
defer closeDB(db) | ||
|
||
exists, err := db.Has([]byte(store.indexName), nil) | ||
if !exists { | ||
log.Printf("Initialized store with index: %v\n", store.count) | ||
return store, nil | ||
} | ||
logrus.Debug(err) | ||
|
||
value, err := db.Get([]byte(store.indexName), nil) | ||
logrus.Debug(err) | ||
|
||
store.count, err = strconv.ParseUint(string(value), 10, 64) | ||
log.Printf("Initialized store with index: %v", store.count) | ||
|
||
return store, err | ||
} | ||
|
||
func (l *LeveldbStore) CreatePuppy(puppy Puppy) (uint64, error) { | ||
if puppy.Value < 0 { | ||
return 0, NewError(ErrInvalidVal) | ||
} | ||
|
||
dataDB, err := openDB(l.path+"/"+l.dataFileName, false) | ||
if err != nil { | ||
return 0, err | ||
} | ||
defer closeDB(dataDB) | ||
|
||
indexDB, err := openDB(l.path+"/"+l.indexFileName, false) | ||
if err != nil { | ||
return 0, err | ||
} | ||
defer closeDB(indexDB) | ||
|
||
l.count++ | ||
puppy.ID = l.count | ||
|
||
encodedPuppy, err := json.Marshal(puppy) | ||
logrus.Debug(err) | ||
|
||
err = dataDB.Put([]byte(strconv.FormatUint(puppy.ID, 10)), encodedPuppy, nil) | ||
logrus.Debug(err) | ||
|
||
err = indexDB.Put([]byte(l.indexName), []byte(strconv.FormatUint(l.count, 10)), nil) | ||
logrus.Debug(err) | ||
|
||
return puppy.ID, nil | ||
} | ||
|
||
func (l *LeveldbStore) ReadPuppy(id uint64) (Puppy, error) { | ||
db, err := openDB(l.path+"/"+l.dataFileName, true) | ||
if err != nil { | ||
return Puppy{}, err | ||
} | ||
defer closeDB(db) | ||
|
||
encoded, err := db.Get([]byte(strconv.FormatUint(id, 10)), nil) | ||
if err != nil { | ||
return Puppy{}, NewError(ErrNotFound, id) | ||
} | ||
|
||
puppy := &Puppy{} | ||
err = json.Unmarshal(encoded, puppy) | ||
logrus.Debug(err) | ||
|
||
return *puppy, nil | ||
} | ||
|
||
func (l *LeveldbStore) UpdatePuppy(id uint64, puppy Puppy) error { | ||
if puppy.Value < 0 { | ||
return NewError(ErrInvalidVal) | ||
} | ||
|
||
db, err := openDB(l.path+"/"+l.dataFileName, false) | ||
if err != nil { | ||
return err | ||
} | ||
defer closeDB(db) | ||
|
||
exists, err := db.Has([]byte(strconv.FormatUint(id, 10)), nil) | ||
if !exists { | ||
return NewError(ErrNotFound, id) | ||
} | ||
logrus.Debug(err) | ||
|
||
puppy.ID = id | ||
encodedPuppy, err := json.Marshal(puppy) | ||
logrus.Debug(err) | ||
|
||
err = db.Put([]byte(strconv.FormatUint(puppy.ID, 10)), encodedPuppy, nil) | ||
logrus.Debug(err) | ||
|
||
return nil | ||
} | ||
|
||
func (l *LeveldbStore) DeletePuppy(id uint64) error { | ||
db, err := openDB(l.path+"/"+l.dataFileName, false) | ||
if err != nil { | ||
return err | ||
} | ||
defer closeDB(db) | ||
|
||
exists, err := db.Has([]byte(strconv.FormatUint(id, 10)), nil) | ||
if !exists { | ||
return NewError(ErrNotFound, id) | ||
} | ||
logrus.Debug(err) | ||
|
||
err = db.Delete([]byte(strconv.FormatUint(id, 10)), nil) | ||
logrus.Debug(err) | ||
|
||
return nil | ||
} | ||
|
||
func openDB(dbPath string, isReadOnly bool) (*leveldb.DB, error) { | ||
db, err := leveldb.OpenFile(dbPath, nil) | ||
if err != nil { | ||
return nil, NewError(ErrOpenDB, dbPath) | ||
} | ||
|
||
if isReadOnly { | ||
err = db.SetReadOnly() | ||
logrus.Debug(err) | ||
} | ||
return db, nil | ||
} | ||
|
||
func closeDB(db *leveldb.DB) { | ||
err := db.Close() | ||
logrus.Debug(err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewLeveldbStoreErrOpen(t *testing.T) { | ||
os.RemoveAll("./test") | ||
_, err := NewLeveldbStore("", "") | ||
assert.Equal(t, "ERROR 501:error occurred while opening db /index.db", err.Error()) | ||
} | ||
|
||
func TestNewLeveldbStoreInitialize(t *testing.T) { | ||
store, _ := NewLeveldbStore("test", "puppy.db") | ||
_, _ = store.CreatePuppy(Puppy{Breed: "Labrador", Colour: "Cream", Value: 2999.99}) | ||
store, _ = NewLeveldbStore("test", "puppy.db") | ||
id, _ := store.CreatePuppy(Puppy{Breed: "Labrador", Colour: "Cream", Value: 2999.99}) | ||
|
||
assert.True(t, id == 2) | ||
os.RemoveAll("./test") | ||
} | ||
|
||
func TestLeveldbStoreCreatePuppyErrOpen(t *testing.T) { | ||
store := LeveldbStore{path: ""} | ||
_, err := store.CreatePuppy(Puppy{}) | ||
assert.Equal(t, "ERROR 501:error occurred while opening db /", err.Error()) | ||
|
||
store.path = "test" | ||
_, err = store.CreatePuppy(Puppy{}) | ||
assert.Equal(t, "ERROR 501:error occurred while opening db test/", err.Error()) | ||
|
||
os.RemoveAll("./test") | ||
} | ||
|
||
func TestLeveldbStoreReadPuppyErrOpen(t *testing.T) { | ||
store := LeveldbStore{path: ""} | ||
_, err := store.ReadPuppy(1) | ||
assert.Equal(t, "ERROR 501:error occurred while opening db /", err.Error()) | ||
|
||
os.RemoveAll("./test") | ||
} | ||
|
||
func TestLeveldbStoreUpdatePuppyErrOpen(t *testing.T) { | ||
store := LeveldbStore{path: ""} | ||
err := store.UpdatePuppy(1, Puppy{}) | ||
assert.Equal(t, "ERROR 501:error occurred while opening db /", err.Error()) | ||
|
||
os.RemoveAll("./test") | ||
} | ||
|
||
func TestLeveldbStoreDeletePuppyErrOpen(t *testing.T) { | ||
store := LeveldbStore{path: ""} | ||
err := store.DeletePuppy(1) | ||
assert.Equal(t, "ERROR 501:error occurred while opening db /", err.Error()) | ||
|
||
os.RemoveAll("./test") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package main | ||
|
||
func NewMapStore() *MapStore { | ||
return &MapStore{count: 0, store: map[uint64]Puppy{}} | ||
} | ||
|
||
func (m *MapStore) CreatePuppy(puppy Puppy) (uint64, error) { | ||
if puppy.Value < 0 { | ||
return 0, NewError(ErrInvalidVal) | ||
} | ||
m.count++ | ||
puppy.ID = m.count | ||
m.store[puppy.ID] = puppy | ||
return puppy.ID, nil | ||
} | ||
|
||
func (m *MapStore) ReadPuppy(id uint64) (Puppy, error) { | ||
puppy, exists := m.store[id] | ||
if exists { | ||
return puppy, nil | ||
} | ||
return puppy, NewError(ErrNotFound, id) | ||
} | ||
|
||
func (m *MapStore) UpdatePuppy(id uint64, puppy Puppy) error { | ||
if puppy.Value < 0 { | ||
return NewError(ErrInvalidVal) | ||
} | ||
|
||
if _, exists := m.store[id]; exists { | ||
puppy.ID = id | ||
m.store[id] = puppy | ||
return nil | ||
} | ||
return NewError(ErrNotFound, id) | ||
} | ||
|
||
func (m *MapStore) DeletePuppy(id uint64) error { | ||
if _, exists := m.store[id]; exists { | ||
delete(m.store, id) | ||
return nil | ||
} | ||
return NewError(ErrNotFound, id) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type StorerTest struct { | ||
suite.Suite | ||
store Storer | ||
id uint64 | ||
} | ||
|
||
func (suite *StorerTest) SetupTest() { | ||
os.RemoveAll("./test") | ||
suite.id, _ = suite.store.CreatePuppy(Puppy{Breed: "Labrador", Colour: "Cream", Value: 2999.99}) | ||
} | ||
|
||
func (suite *StorerTest) TearDownSuite() { | ||
os.RemoveAll("./test") | ||
} | ||
|
||
func (suite *StorerTest) TestCreatePuppy() { | ||
id, err := suite.store.CreatePuppy(Puppy{Breed: "German Shepard", Colour: "Brown", Value: 3499.99}) | ||
suite.True(id > 1) | ||
suite.Nil(err) | ||
|
||
id, err = suite.store.CreatePuppy(Puppy{Breed: "Terrier", Colour: "White", Value: -3499.99}) | ||
suite.True(id == 0) | ||
suite.Equal("ERROR 400:value cannot be negative", err.Error()) | ||
} | ||
|
||
func (suite *StorerTest) TestReadPuppy() { | ||
puppy, err := suite.store.ReadPuppy(suite.id) | ||
|
||
suite.Nil(err) | ||
suite.Equal(puppy.ID, suite.id) | ||
suite.Equal(puppy.Breed, "Labrador") | ||
suite.Equal(puppy.Colour, "Cream") | ||
suite.Equal(puppy.Value, 2999.99) | ||
|
||
_, err = suite.store.ReadPuppy(100) | ||
suite.Equal("ERROR 404:puppy with id=100 not found", err.Error()) | ||
} | ||
|
||
func (suite *StorerTest) TestUpdatePuppy() { | ||
err := suite.store.UpdatePuppy(suite.id, Puppy{Breed: "Labrador Retriever", Colour: "Brown", Value: 3999.99}) | ||
|
||
suite.Nil(err) | ||
puppy, err := suite.store.ReadPuppy(suite.id) | ||
|
||
suite.Nil(err) | ||
suite.Equal(puppy.ID, suite.id) | ||
suite.Equal(puppy.Breed, "Labrador Retriever") | ||
suite.Equal(puppy.Colour, "Brown") | ||
suite.Equal(puppy.Value, 3999.99) | ||
|
||
err = suite.store.UpdatePuppy(suite.id, Puppy{Breed: "Poodle", Colour: "White", Value: -1999.99}) | ||
suite.Equal("ERROR 400:value cannot be negative", err.Error()) | ||
|
||
err = suite.store.UpdatePuppy(100, Puppy{Breed: "Poodle", Colour: "White", Value: 1999.99}) | ||
suite.Equal("ERROR 404:puppy with id=100 not found", err.Error()) | ||
} | ||
|
||
func (suite *StorerTest) TestDeletePuppy() { | ||
err := suite.store.DeletePuppy(suite.id) | ||
suite.Nil(err) | ||
|
||
_, err = suite.store.ReadPuppy(suite.id) | ||
suite.Equal(fmt.Sprintf("ERROR 404:puppy with id=%v not found", suite.id), err.Error()) | ||
|
||
err = suite.store.DeletePuppy(suite.id) | ||
suite.Equal(fmt.Sprintf("ERROR 404:puppy with id=%v not found", suite.id), err.Error()) | ||
} | ||
|
||
func TestMapStore(t *testing.T) { | ||
s := StorerTest{ | ||
store: NewMapStore(), | ||
} | ||
suite.Run(t, &s) | ||
} | ||
|
||
func TestSyncStore(t *testing.T) { | ||
s := StorerTest{ | ||
store: NewSyncStore(), | ||
} | ||
suite.Run(t, &s) | ||
} | ||
|
||
func TestLevelDBStore(t *testing.T) { | ||
store, _ := NewLeveldbStore("./test", "puppy.db") | ||
s := StorerTest{ | ||
store: store, | ||
} | ||
suite.Run(t, &s) | ||
} |
Oops, something went wrong.