Skip to content

Commit

Permalink
test: add tests for couchbase client adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
Dorian committed Jul 28, 2023
1 parent fb46695 commit fd4be46
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 8 deletions.
95 changes: 87 additions & 8 deletions src/database/couchbase/client.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package database
package couchbase

import (
"log"
Expand All @@ -12,12 +12,89 @@ import (
const DEFAULT_SCOPE = "redirects"
const DEFAULT_COLLECTION = "personal"

type CouchbaseAdapter struct {
client *gocb.Cluster
type GetResultAdapterInterface interface {
Content(valuePtr interface{}) error
}

type CollectionAdapterInterface interface {
Get(id string, opts *gocb.GetOptions) (docOut GetResultAdapterInterface, errOut error)
Upsert(id string, val interface{}, opts *gocb.UpsertOptions) (mutOut *gocb.MutationResult, errOut error)
}

type collectionAdapter struct {
collection *gocb.Collection
}

func (ca *collectionAdapter) Get(id string, opts *gocb.GetOptions) (docOut GetResultAdapterInterface, errOut error) {
return ca.collection.Get(id, opts)
}

func (ca *collectionAdapter) Upsert(id string, val interface{}, opts *gocb.UpsertOptions) (mutOut *gocb.MutationResult, errOut error) {
return ca.collection.Upsert(id, val, opts)
}

type ScopeAdapterInterface interface {
Collection(collectionName string) CollectionAdapterInterface
}

type scopeAdapter struct {
scope *gocb.Scope
}

func (sa *scopeAdapter) Collection(collectionName string) CollectionAdapterInterface {
return &collectionAdapter{
collection: sa.scope.Collection(collectionName),
}
}

type BucketAdapterInterface interface {
WaitUntilReady(timeout time.Duration, opts *gocb.WaitUntilReadyOptions) error
Scope(scopeName string) ScopeAdapterInterface
}

type bucketAdater struct {
bucket *gocb.Bucket
}

func (ba *bucketAdater) WaitUntilReady(timeout time.Duration, opts *gocb.WaitUntilReadyOptions) error {
return ba.bucket.WaitUntilReady(timeout, opts)
}

func (ba *bucketAdater) Scope(scopeName string) ScopeAdapterInterface {
return &scopeAdapter{
scope: ba.bucket.Scope(scopeName),
}
}

type CouchbaseClientAdapterInterface interface {
Bucket(bucketName string) BucketAdapterInterface
Close(opts *gocb.ClusterCloseOptions) error
}

type couchbaseClientAdapter struct {
client *gocb.Cluster
}

func (cca *couchbaseClientAdapter) Bucket(bucketName string) BucketAdapterInterface {
return &bucketAdater{
bucket: cca.client.Bucket(bucketName),
}
}

func (cca *couchbaseClientAdapter) Close(opts *gocb.ClusterCloseOptions) error {
return cca.client.Close(opts)
}

type couchbaseAdapter struct {
client CouchbaseClientAdapterInterface
bucketName string
}

func (ca *CouchbaseAdapter) getClient() *gocb.Cluster {
func NewCouchbaseAdapter() *couchbaseAdapter {
return &couchbaseAdapter{}
}

func (ca *couchbaseAdapter) getClient() CouchbaseClientAdapterInterface {
if ca.client != nil {
return ca.client
}
Expand All @@ -37,16 +114,18 @@ func (ca *CouchbaseAdapter) getClient() *gocb.Cluster {
}

ca.bucketName = os.Getenv("COUCHBASE_BUCKET")
ca.client = cluster
ca.client = &couchbaseClientAdapter{
client: cluster,
}

return ca.client
}

func (ca *CouchbaseAdapter) Close() error {
func (ca *couchbaseAdapter) Close() error {
return ca.getClient().Close(nil)
}

func (ca *CouchbaseAdapter) Read(documentRef string) (*document.ReadOutput, error) {
func (ca *couchbaseAdapter) Read(documentRef string) (*document.ReadOutput, error) {
bucket := ca.getClient().Bucket(ca.bucketName)

err := bucket.WaitUntilReady(5*time.Second, nil)
Expand All @@ -71,7 +150,7 @@ func (ca *CouchbaseAdapter) Read(documentRef string) (*document.ReadOutput, erro
return &document.ReadOutput{Data: data}, nil
}

func (ca *CouchbaseAdapter) Write(documentRef string, data interface{}) (interface{}, error) {
func (ca *couchbaseAdapter) Write(documentRef string, data interface{}) (interface{}, error) {
bucket := ca.getClient().Bucket(ca.bucketName)

err := bucket.WaitUntilReady(5*time.Second, nil)
Expand Down
227 changes: 227 additions & 0 deletions src/database/couchbase/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package couchbase

import (
"errors"
"reflect"
"testing"
"time"

"github.com/couchbase/gocb/v2"
)

var (
haveCloseBeenCalled = false
readyFn func() error
scopeFn func() ScopeAdapterInterface
getFn func() (docOut GetResultAdapterInterface, errOut error)
upsertFn func() (mutOut *gocb.MutationResult, errOut error)
contentFn func() error
)

type contentAdapterMock struct{}

func (cam *contentAdapterMock) Content(valuePtr interface{}) error {
return contentFn()
}

type collectionAdapterMock struct{}

func (cam *collectionAdapterMock) Get(id string, opts *gocb.GetOptions) (docOut GetResultAdapterInterface, errOut error) {
return getFn()
}

func (cam *collectionAdapterMock) Upsert(id string, val interface{}, opts *gocb.UpsertOptions) (mutOut *gocb.MutationResult, errOut error) {
return upsertFn()
}

type scopeAdapterMock struct{}

func (sam *scopeAdapterMock) Collection(collectionName string) CollectionAdapterInterface {
return &collectionAdapterMock{}
}

type bucketAdapterMock struct{}

func (a *bucketAdapterMock) WaitUntilReady(timeout time.Duration, opts *gocb.WaitUntilReadyOptions) error {
return readyFn()
}

func (a *bucketAdapterMock) Scope(scopeName string) ScopeAdapterInterface {
return scopeFn()
}

type clientAdapterMock struct{}

func (c *clientAdapterMock) Bucket(bucketName string) BucketAdapterInterface {
return &bucketAdapterMock{}
}

func (c *clientAdapterMock) Close(opts *gocb.ClusterCloseOptions) error {
haveCloseBeenCalled = true
return nil
}

func resetMocks() {
haveCloseBeenCalled = false
contentFn = func() error {
return nil
}
getFn = func() (docOut GetResultAdapterInterface, errOut error) {
return &contentAdapterMock{}, nil
}
upsertFn = func() (mutOut *gocb.MutationResult, errOut error) {
return nil, nil
}
scopeFn = func() ScopeAdapterInterface {
return &scopeAdapterMock{}
}
readyFn = func() error {
return nil
}
}

func TestClientClose(t *testing.T) {
defer t.Cleanup(resetMocks)

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

c.Close()

if !haveCloseBeenCalled {
t.Error("Method Close() is expected to be called")
}
}

func TestClientRead(t *testing.T) {
defer t.Cleanup(resetMocks)

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

got, err := c.Read("foo")

if err != nil {
t.Errorf("An error is not expected")
}

if reflect.TypeOf(got).String() != "*document.ReadOutput" {
t.Error("The return expected to be instance of *document.ReadOutput")
}
}

func TestClientCannotReadBecauseItIsNotReady(t *testing.T) {
defer t.Cleanup(resetMocks)

readyFn = func() error {
return errors.New("something goes wrong")
}

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

got, err := c.Read("foo")

if err == nil {
t.Errorf("An error is expected")
}

if got != nil {
t.Error("The return expected to be nil")
}
}

func TestClientCannotReadWhenDocumentCannotBeFound(t *testing.T) {
defer t.Cleanup(resetMocks)

getFn = func() (docOut GetResultAdapterInterface, errOut error) {
return nil, errors.New("something goes wrong")
}

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

got, err := c.Read("foo")

if err == nil {
t.Errorf("An error is expected")
}

if got != nil {
t.Error("The return expected to be nil")
}
}

func TestClientCannotReadWhenContentCannotBeAssigned(t *testing.T) {
defer t.Cleanup(resetMocks)

contentFn = func() error {
return errors.New("something goes wrong")
}

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

got, err := c.Read("foo")

if err == nil {
t.Errorf("An error is expected")
}

if got != nil {
t.Error("The return expected to be nil")
}
}

func TestClientWrite(t *testing.T) {
defer t.Cleanup(resetMocks)

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

got, err := c.Write("foo", map[string]int{"foo": 1, "bar": 2})

if err != nil {
t.Errorf("An error is not expected")
}

_, ok := got.(map[string]int)

if !ok {
t.Error("The return expected to be map[string]int")
}
}

func TestClientCannotWriteBecauseItIsNotReady(t *testing.T) {
defer t.Cleanup(resetMocks)

readyFn = func() error {
return errors.New("something goes wrong")
}

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

_, err := c.Write("foo", nil)

if err == nil {
t.Errorf("An error is expected")
}
}

func TestClientCannotWriteBecauseInsertWentWrongOnDatabaseSide(t *testing.T) {
defer t.Cleanup(resetMocks)

upsertFn = func() (mutOut *gocb.MutationResult, errOut error) {
return nil, errors.New("something goes wrong")
}

c := NewCouchbaseAdapter()
c.client = &clientAdapterMock{}

_, err := c.Write("foo", nil)

if err == nil {
t.Errorf("An error is expected")
}
}

0 comments on commit fd4be46

Please sign in to comment.