Skip to content

Commit

Permalink
Merge pull request #228 from carolynvs/managed-store
Browse files Browse the repository at this point in the history
Extract ManagedStore interface from BackingStore
  • Loading branch information
carolynvs-msft authored Aug 17, 2020
2 parents 7d2adab + 998622e commit 9005c1d
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 10 deletions.
8 changes: 4 additions & 4 deletions claim/claimstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ var (

// Store is a persistent store for claims.
type Store struct {
backingStore *crud.BackingStore
backingStore crud.ManagedStore
encrypt EncryptionHandler
decrypt EncryptionHandler
}

// NewClaimStore creates a persistent store for claims using the specified
// backing key-blob store.
func NewClaimStore(store *crud.BackingStore, encrypt EncryptionHandler, decrypt EncryptionHandler) Store {
// backing datastore.
func NewClaimStore(store crud.ManagedStore, encrypt EncryptionHandler, decrypt EncryptionHandler) Store {
if encrypt == nil {
encrypt = noOpEncryptionHandler
}
Expand Down Expand Up @@ -81,7 +81,7 @@ var noOpEncryptionHandler = func(data []byte) ([]byte, error) {
}

// GetBackingStore returns the data store behind this claim store.
func (s Store) GetBackingStore() *crud.BackingStore {
func (s Store) GetBackingStore() crud.ManagedStore {
return s.backingStore
}

Expand Down
6 changes: 3 additions & 3 deletions credentials/credstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ var ErrNotFound = errors.New("Credential set does not exist")

// Store is a persistent store for credential sets.
type Store struct {
backingStore *crud.BackingStore
backingStore crud.ManagedStore
}

// NewCredentialStore creates a persistent store for credential sets using the specified
// backing key-blob store.
func NewCredentialStore(store *crud.BackingStore) Store {
func NewCredentialStore(store crud.ManagedStore) Store {
return Store{
backingStore: store,
}
}

// GetBackingStore returns the data store behind this credentials store.
func (s Store) GetBackingStore() *crud.BackingStore {
func (s Store) GetBackingStore() crud.ManagedStore {
return s.backingStore
}

Expand Down
13 changes: 11 additions & 2 deletions utils/crud/backingstore.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package crud

var _ Store = &BackingStore{}
var _ ManagedStore = &BackingStore{}

// BackingStore wraps another store that may have Connect/Close methods that
// need to be called.
// - Connect is called before a method when the connection is closed.
// - Close is called after each method when AutoClose is true (default).
type BackingStore struct {

// AutoClose specifies if the connection should be automatically
// closed when done accessing the backing store.
AutoClose bool
Expand Down Expand Up @@ -75,6 +74,16 @@ func (s *BackingStore) autoClose() error {
return nil
}

func (s *BackingStore) Count(itemType string, group string) (int, error) {
handleClose, err := s.HandleConnect()
defer handleClose()
if err != nil {
return 0, err
}

return s.datastore.Count(itemType, group)
}

func (s *BackingStore) List(itemType string, group string) ([]string, error) {
handleClose, err := s.HandleConnect()
defer handleClose()
Expand Down
22 changes: 22 additions & 0 deletions utils/crud/backingstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,25 @@ func TestBackingStore_ReadAll(t *testing.T) {
})
}
}

func TestBackingStore_Count(t *testing.T) {
s := NewMockStore()

count, err := s.Count(testItemType, testGroup)
require.NoError(t, err, "Count failed")
assert.Equal(t, 0, count, "Count should be 0 for an empty datastore")

err = s.Save(testItemType, testGroup, "key1", []byte("value1"))
require.NoError(t, err, "Save failed")

count, err = s.Count(testItemType, testGroup)
require.NoError(t, err, "Count failed")
assert.Equal(t, 1, count, "Count should be 1 after adding an item")

err = s.Delete(testItemType, "key1")
require.NoError(t, err, "Delete failed")

count, err = s.Count(testItemType, testGroup)
require.NoError(t, err, "Count failed")
assert.Equal(t, 0, count, "Count should be 0 after deleting the item")
}
5 changes: 5 additions & 0 deletions utils/crud/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ type FileSystemStore struct {
fileExtensions map[string]string
}

func (s FileSystemStore) Count(itemType string, group string) (int, error) {
names, err := s.List(itemType, group)
return len(names), err
}

func (s FileSystemStore) List(itemType string, group string) ([]string, error) {
if err := s.ensure(itemType); err != nil {
return nil, err
Expand Down
28 changes: 27 additions & 1 deletion utils/crud/filesystem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"testing"

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

var _ Store = FileSystemStore{}

func TestFilesystemStore(t *testing.T) {
is := assert.New(t)
tmdir, err := ioutil.TempDir("", "duffle-test-")
tmdir, err := ioutil.TempDir("", "cnab-test-")
is.NoError(err)
defer os.RemoveAll(tmdir)
s := NewFileSystemStore(tmdir, map[string]string{testItemType: ".json"})
Expand All @@ -30,3 +31,28 @@ func TestFilesystemStore(t *testing.T) {
is.NoError(err)
is.Len(list, 0)
}

func TestFileSystemStore_Count(t *testing.T) {
tmdir, err := ioutil.TempDir("", "cnab-test-")
require.NoError(t, err)
defer os.RemoveAll(tmdir)
s := NewFileSystemStore(tmdir, map[string]string{testItemType: ".json"})

count, err := s.Count(testItemType, "")
require.NoError(t, err, "Count failed")
assert.Equal(t, 0, count, "Count should be 0 for an empty datastore")

err = s.Save(testItemType, "", "key1", []byte("value1"))
require.NoError(t, err, "Save failed")

count, err = s.Count(testItemType, "")
require.NoError(t, err, "Count failed")
assert.Equal(t, 1, count, "Count should be 1 after adding an item")

err = s.Delete(testItemType, "key1")
require.NoError(t, err, "Delete failed")

count, err = s.Count(testItemType, "")
require.NoError(t, err, "Count failed")
assert.Equal(t, 0, count, "Count should be 0 after deleting the item")
}
5 changes: 5 additions & 0 deletions utils/crud/mock_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ func (s MockStore) key(itemType string, id string) string {
return path.Join(itemType, id)
}

func (s MockStore) Count(itemType string, group string) (int, error) {
names, err := s.List(itemType, group)
return len(names), err
}

func (s MockStore) List(itemType string, group string) ([]string, error) {
if s.ListMock != nil {
return s.ListMock(itemType, group)
Expand Down
23 changes: 23 additions & 0 deletions utils/crud/mock_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

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

func TestMockStoreWithGroups(t *testing.T) {
Expand Down Expand Up @@ -49,3 +50,25 @@ func TestMockStoreWithoutGroups(t *testing.T) {
is.NoError(err)
is.Equal(groups, []string{"test"})
}

func TestMockStore_Count(t *testing.T) {
s := NewMockStore()

count, err := s.Count(testItemType, "")
require.NoError(t, err, "Count failed")
assert.Equal(t, 0, count, "Count should be 0 for an empty datastore")

err = s.Save(testItemType, "", "key1", []byte("value1"))
require.NoError(t, err, "Save failed")

count, err = s.Count(testItemType, "")
require.NoError(t, err, "Count failed")
assert.Equal(t, 1, count, "Count should be 1 after adding an item")

err = s.Delete(testItemType, "key1")
require.NoError(t, err, "Delete failed")

count, err = s.Count(testItemType, "")
require.NoError(t, err, "Count failed")
assert.Equal(t, 0, count, "Count should be 0 after deleting the item")
}
13 changes: 13 additions & 0 deletions utils/crud/mongodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ func (s *mongoDBStore) getCollection(itemType string) *mgo.Collection {
return c
}

func (s *mongoDBStore) Count(itemType string, group string) (int, error) {
collection := s.getCollection(itemType)

var query map[string]string
if group != "" {
query = map[string]string{
"group": group,
}
}
n, err := collection.Find(query).Count()
return n, wrapErr(err)
}

func (s *mongoDBStore) List(itemType string, group string) ([]string, error) {
collection := s.getCollection(itemType)

Expand Down
29 changes: 29 additions & 0 deletions utils/crud/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,41 @@ package crud

// Store is a simplified interface to a key-blob store supporting CRUD operations.
type Store interface {
// Count the number of items of the optional type and group.
Count(itemType string, group string) (int, error)

// List the names of the items of the optional type and group.
List(itemType string, group string) ([]string, error)

// Save an item's data using the specified name with optional metadata
// identifying it with an item type and group.
Save(itemType string, group string, name string, data []byte) error

// Read the data for a named item of the optional type.
Read(itemType string, name string) ([]byte, error)

// Delete a named item of the optional type.
Delete(itemType string, name string) error
}

// ManagedStore is a wrapped crud.Store with a managed connection lifecycle.
type ManagedStore interface {
// Store is the underlying datastore.
Store

// ReadAll retrieves all the items with the optional item type.
ReadAll(itemType string, group string) ([][]byte, error)

// GetDataStore returns the datastore managed by this instance.
GetDataStore() Store

// HandleConnect connects if necessary, returning a function to close the
// connection. This close function may be a no-op when connection was
// already established and this call to Connect isn't managing the
// connection.
HandleConnect() (func() error, error)
}

// HasConnect indicates that a struct must be initialized using the Connect
// method before the interface's methods are called.
type HasConnect interface {
Expand Down

0 comments on commit 9005c1d

Please sign in to comment.