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 }