github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/cassandra/datastore_crud_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package cassandra_test
    22  
    23  import (
    24  	"context"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/pkg/errors"
    29  	gouuid "github.com/satori/go.uuid"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/uber-go/dosa"
    32  )
    33  
    34  var (
    35  	defaultStringKeyValue       = "apple"
    36  	defaultInt64KeyValue  int64 = 100
    37  )
    38  
    39  func TestReadNotFound(t *testing.T) {
    40  	sut := GetTestConnector(t)
    41  	id := constructKeys(dosa.UUID(gouuid.NewV4().String()))
    42  	_, err := sut.Read(context.TODO(), testEntityInfo, id, []string{int32Field})
    43  	assert.Error(t, err)
    44  	assert.IsType(t, &dosa.ErrNotFound{}, errors.Cause(err), err.Error())
    45  }
    46  
    47  func TestReadTimeout(t *testing.T) {
    48  	sut := GetTestConnector(t)
    49  	id := constructKeys(dosa.UUID(gouuid.NewV4().String()))
    50  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond)
    51  	defer cancel()
    52  	_, err := sut.Read(ctx, testEntityInfo, id, []string{int32Field})
    53  	assert.Error(t, err)
    54  	assert.Contains(t, err.Error(), "context deadline exceeded")
    55  }
    56  
    57  func TestUpsertAndRead(t *testing.T) {
    58  	sut := GetTestConnector(t)
    59  	uuid := dosa.UUID(gouuid.NewV4().String())
    60  	values := constructFullValues(uuid)
    61  	err := sut.Upsert(context.TODO(), testEntityInfo, values)
    62  	assert.NoError(t, err)
    63  
    64  	id := constructKeys(uuid)
    65  	allValueFields := []string{int32Field, doubleField, blobField, boolField, timestampField, int64Field, stringField, uuidField}
    66  	readRes, err := sut.Read(context.TODO(), testEntityInfo, id, allValueFields)
    67  	assert.NoError(t, err)
    68  	assert.Equal(t, len(allValueFields), len(readRes))
    69  	for _, field := range allValueFields {
    70  		assert.Equal(t, values[field], readRes[field])
    71  	}
    72  
    73  	// partial update first object
    74  	pu := map[string]dosa.FieldValue{
    75  		uuidKeyField:   uuid,
    76  		stringKeyField: defaultStringKeyValue,
    77  		int64KeyField:  defaultInt64KeyValue,
    78  		int32Field:     int32(-100), // update to -100
    79  	}
    80  
    81  	// partial create second object
    82  	uuid2 := dosa.UUID(gouuid.NewV4().String())
    83  	pc2 := map[string]dosa.FieldValue{
    84  		uuidKeyField:   uuid2,
    85  		stringKeyField: defaultStringKeyValue,
    86  		int64KeyField:  defaultInt64KeyValue,
    87  		int32Field:     int32(-100),
    88  	}
    89  
    90  	err = sut.Upsert(context.TODO(), testEntityInfo, pu)
    91  	assert.NoError(t, err)
    92  	err = sut.Upsert(context.TODO(), testEntityInfo, pc2)
    93  	assert.NoError(t, err)
    94  
    95  	// check updated int32 field
    96  	updated, err := sut.Read(context.TODO(), testEntityInfo, id, []string{int32Field})
    97  	assert.NoError(t, err)
    98  	assert.Len(t, updated, 1)
    99  	assert.Equal(t, updated[int32Field], int32(-100))
   100  
   101  	// read second object with nil `fieldsToRead`
   102  	id2 := constructKeys(uuid2)
   103  	res, err := sut.Read(context.TODO(), testEntityInfo, id2, nil)
   104  	assert.NoError(t, err)
   105  	assert.Equal(t, len(allValueFields), len(res)) // should read all non-key fields
   106  	expectedPartialValues := map[string]dosa.FieldValue{
   107  		int32Field: int32(-100),
   108  		// the following are all zero values
   109  		int64Field:     int64(0),
   110  		doubleField:    float64(0.0),
   111  		blobField:      []byte{},
   112  		boolField:      false,
   113  		timestampField: time.Time{},
   114  		stringField:    "",
   115  		uuidField:      dosa.UUID("00000000-0000-0000-0000-000000000000"),
   116  	}
   117  	assert.Equal(t, expectedPartialValues, res)
   118  }
   119  
   120  func TestCreateIfNotExists(t *testing.T) {
   121  	sut := GetTestConnector(t)
   122  	uuid := dosa.UUID(gouuid.NewV4().String())
   123  	values := constructFullValues(uuid)
   124  	err := sut.CreateIfNotExists(context.TODO(), testEntityInfo, values)
   125  	assert.NoError(t, err)
   126  
   127  	id := constructKeys(uuid)
   128  	allValueFields := []string{int32Field, doubleField, blobField, boolField, timestampField, int64Field, stringField, uuidField}
   129  	readRes, err := sut.Read(context.TODO(), testEntityInfo, id, allValueFields)
   130  	assert.NoError(t, err)
   131  	assert.Equal(t, len(allValueFields), len(readRes))
   132  	for _, field := range allValueFields {
   133  		assert.Equal(t, values[field], readRes[field])
   134  	}
   135  
   136  	// should fail if already exists
   137  	err = sut.CreateIfNotExists(context.TODO(), testEntityInfo, values)
   138  	assert.Error(t, err)
   139  	assert.IsType(t, &dosa.ErrAlreadyExists{}, err)
   140  }
   141  
   142  func TestDelete(t *testing.T) {
   143  	sut := GetTestConnector(t)
   144  	uuid1 := dosa.UUID(gouuid.NewV4().String())
   145  	id1 := constructKeys(uuid1)
   146  	v1 := constructFullValues(uuid1)
   147  
   148  	err := sut.Upsert(context.TODO(), testEntityInfo, v1)
   149  	assert.NoError(t, err)
   150  
   151  	_, err = sut.Read(context.TODO(), testEntityInfo, id1, []string{int32Field})
   152  	assert.NoError(t, err)
   153  
   154  	err = sut.Remove(context.TODO(), testEntityInfo, id1)
   155  	assert.NoError(t, err)
   156  
   157  	_, err = sut.Read(context.TODO(), testEntityInfo, id1, []string{int32Field})
   158  	assert.Error(t, err)
   159  	assert.IsType(t, &dosa.ErrNotFound{}, err)
   160  
   161  	// no-op
   162  	err = sut.Remove(context.TODO(), testEntityInfo, id1)
   163  	assert.NoError(t, err)
   164  }
   165  
   166  func TestRemoveRange(t *testing.T) {
   167  	sut := GetTestConnector(t)
   168  	uuid1 := dosa.UUID(gouuid.NewV4().String())
   169  	v1 := constructFullValues(uuid1)
   170  	v1[int64KeyField] = 1
   171  	v2 := constructFullValues(uuid1)
   172  	v2[int64KeyField] = 2
   173  	v3 := constructFullValues(uuid1)
   174  	v3[int64KeyField] = 3
   175  
   176  	// Test RemoveRange with no entities in range
   177  	conds1 := map[string][]*dosa.Condition{
   178  		uuidKeyField:   {&dosa.Condition{Op: dosa.Eq, Value: uuid1}},
   179  		stringKeyField: {&dosa.Condition{Op: dosa.Eq, Value: defaultStringKeyValue}},
   180  	}
   181  	err := sut.RemoveRange(context.TODO(), testEntityInfo, conds1)
   182  
   183  	// Upsert all values
   184  	err = sut.Upsert(context.TODO(), testEntityInfo, v1)
   185  	assert.NoError(t, err)
   186  
   187  	err = sut.Upsert(context.TODO(), testEntityInfo, v2)
   188  	assert.NoError(t, err)
   189  
   190  	err = sut.Upsert(context.TODO(), testEntityInfo, v3)
   191  	assert.NoError(t, err)
   192  
   193  	// Remove Values larger than 200
   194  	conds2 := map[string][]*dosa.Condition{
   195  		uuidKeyField:   {&dosa.Condition{Op: dosa.Eq, Value: uuid1}},
   196  		stringKeyField: {&dosa.Condition{Op: dosa.Eq, Value: defaultStringKeyValue}},
   197  		int64KeyField:  {&dosa.Condition{Op: dosa.Gt, Value: 2}},
   198  	}
   199  	err = sut.RemoveRange(context.TODO(), testEntityInfo, conds2)
   200  	assert.NoError(t, err)
   201  
   202  	// Ensure 100 and 200 are still in the range
   203  	conds3 := map[string][]*dosa.Condition{
   204  		uuidKeyField:   {&dosa.Condition{Op: dosa.Eq, Value: uuid1}},
   205  		stringKeyField: {&dosa.Condition{Op: dosa.Eq, Value: defaultStringKeyValue}},
   206  	}
   207  
   208  	res, _, err := testStore.Range(context.TODO(), testEntityInfo, conds3, dosa.All(), "", 5)
   209  	assert.NoError(t, err)
   210  	assert.Len(t, res, 2)
   211  	assert.Equal(t, int64(2), res[0][int64KeyField])
   212  	assert.Equal(t, int64(1), res[1][int64KeyField])
   213  
   214  	// Remove Values less than 200
   215  	conds4 := map[string][]*dosa.Condition{
   216  		uuidKeyField:   {&dosa.Condition{Op: dosa.Eq, Value: uuid1}},
   217  		stringKeyField: {&dosa.Condition{Op: dosa.Eq, Value: defaultStringKeyValue}},
   218  		int64KeyField:  {&dosa.Condition{Op: dosa.Lt, Value: 2}},
   219  	}
   220  	err = sut.RemoveRange(context.TODO(), testEntityInfo, conds4)
   221  	assert.NoError(t, err)
   222  
   223  	// Ensure the 200 value is still in the range. It's the only value that should be left.
   224  	conds5 := map[string][]*dosa.Condition{
   225  		uuidKeyField:   {&dosa.Condition{Op: dosa.Eq, Value: uuid1}},
   226  		stringKeyField: {&dosa.Condition{Op: dosa.Eq, Value: defaultStringKeyValue}},
   227  	}
   228  
   229  	res, _, err = sut.Range(context.TODO(), testEntityInfo, conds5, dosa.All(), "", 5)
   230  	assert.NoError(t, err)
   231  	assert.Len(t, res, 1)
   232  	assert.Equal(t, int64(2), res[0][int64KeyField])
   233  }
   234  
   235  func constructFullValues(uuid dosa.UUID) map[string]dosa.FieldValue {
   236  	return map[string]dosa.FieldValue{
   237  		uuidKeyField:   uuid,
   238  		stringKeyField: defaultStringKeyValue,
   239  		int64KeyField:  defaultInt64KeyValue,
   240  		int32Field:     int32(100),
   241  		int64Field:     int64(100),
   242  		doubleField:    float64(100.0),
   243  		blobField:      []byte("blob"),
   244  		boolField:      true,
   245  		// TODO: store timestamp using int64 or else declare we only support ms resolution
   246  		// Anything smaller than ms is lost.
   247  		timestampField: time.Unix(100, 111000000).UTC(),
   248  		stringField:    "appleV",
   249  		uuidField:      dosa.UUID(gouuid.NewV4().String()),
   250  	}
   251  }
   252  
   253  func constructKeys(uuid dosa.UUID) map[string]dosa.FieldValue {
   254  	return map[string]dosa.FieldValue{
   255  		uuidKeyField:   uuid,
   256  		int64KeyField:  defaultInt64KeyValue,
   257  		stringKeyField: defaultStringKeyValue,
   258  	}
   259  }