ducky/devices
ducky/devices/storer_test.go
Return ErrDeviceNotFound when updating devices. If we can't find the Device we're supposed to update, return an ErrDeviceNotFound error. Write a unit test that tests for this behaviour.
| paddy@2 | 1 package devices |
| paddy@2 | 2 |
| paddy@2 | 3 import ( |
| paddy@10 | 4 "fmt" |
| paddy@2 | 5 "testing" |
| paddy@2 | 6 "time" |
| paddy@2 | 7 |
| paddy@2 | 8 "code.secondbit.org/uuid.hg" |
| paddy@2 | 9 "golang.org/x/net/context" |
| paddy@2 | 10 ) |
| paddy@2 | 11 |
| paddy@9 | 12 type StorerFactory interface { |
| paddy@9 | 13 NewStorer(ctx context.Context) (Storer, error) |
| paddy@9 | 14 TeardownStorer(storer Storer, ctx context.Context) error |
| paddy@9 | 15 } |
| paddy@9 | 16 |
| paddy@9 | 17 var storerFactories []StorerFactory |
| paddy@2 | 18 |
| paddy@10 | 19 const ( |
| paddy@10 | 20 changeName = 1 << iota |
| paddy@10 | 21 changeOwner |
| paddy@10 | 22 changeType |
| paddy@10 | 23 changeCreated |
| paddy@10 | 24 changeLastSeen |
| paddy@10 | 25 changePushToken |
| paddy@10 | 26 changeVariations |
| paddy@10 | 27 ) |
| paddy@10 | 28 |
| paddy@2 | 29 func compareDevices(device1, device2 Device) (ok bool, field string, expected, result interface{}) { |
| paddy@2 | 30 if !device1.ID.Equal(device2.ID) { |
| paddy@2 | 31 return false, "ID", device1.ID, device2.ID |
| paddy@2 | 32 } |
| paddy@2 | 33 if device1.Name != device2.Name { |
| paddy@2 | 34 return false, "Name", device1.Name, device2.Name |
| paddy@2 | 35 } |
| paddy@2 | 36 if !device1.Owner.Equal(device2.Owner) { |
| paddy@2 | 37 return false, "Owner", device1.Owner, device2.Owner |
| paddy@2 | 38 } |
| paddy@2 | 39 if device1.Type != device2.Type { |
| paddy@2 | 40 return false, "Type", device1.Type, device2.Type |
| paddy@2 | 41 } |
| paddy@2 | 42 if !device1.Created.Equal(device2.Created) { |
| paddy@2 | 43 return false, "Created", device1.Created, device2.Created |
| paddy@2 | 44 } |
| paddy@2 | 45 if !device1.LastSeen.Equal(device2.LastSeen) { |
| paddy@2 | 46 return false, "LastSeen", device1.LastSeen, device2.LastSeen |
| paddy@2 | 47 } |
| paddy@2 | 48 if device1.PushToken != device2.PushToken { |
| paddy@2 | 49 return false, "PushToken", device1.PushToken, device2.PushToken |
| paddy@2 | 50 } |
| paddy@2 | 51 return true, "", nil, nil |
| paddy@2 | 52 } |
| paddy@2 | 53 |
| paddy@5 | 54 func TestCreateAndGetDevices(t *testing.T) { |
| paddy@9 | 55 for _, factory := range storerFactories { |
| paddy@10 | 56 ctx := context.Background() |
| paddy@10 | 57 storer, err := factory.NewStorer(ctx) |
| paddy@2 | 58 if err != nil { |
| paddy@9 | 59 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@2 | 60 } |
| paddy@2 | 61 |
| paddy@2 | 62 devices := []Device{ |
| paddy@2 | 63 {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@2 | 64 {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@2 | 65 {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@2 | 66 } |
| paddy@3 | 67 |
| paddy@10 | 68 err = storer.CreateDevices(devices, ctx) |
| paddy@3 | 69 if err != nil { |
| paddy@5 | 70 t.Errorf("Error creating devices in %T: %+v\n", storer, err) |
| paddy@3 | 71 } |
| paddy@3 | 72 |
| paddy@2 | 73 ids := make([]uuid.ID, 0, len(devices)) |
| paddy@2 | 74 for _, device := range devices { |
| paddy@2 | 75 ids = append(ids, device.ID) |
| paddy@2 | 76 } |
| paddy@2 | 77 |
| paddy@10 | 78 results, err := storer.GetDevices(ids, ctx) |
| paddy@2 | 79 if err != nil { |
| paddy@2 | 80 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err) |
| paddy@2 | 81 } |
| paddy@2 | 82 for _, device := range devices { |
| paddy@2 | 83 d, returned := results[device.ID.String()] |
| paddy@2 | 84 if !returned { |
| paddy@2 | 85 t.Errorf("Expected device %s to be in results from %T, but wasn't present\n", device.Name, storer) |
| paddy@2 | 86 } |
| paddy@2 | 87 ok, field, expected, result := compareDevices(device, d) |
| paddy@2 | 88 if !ok { |
| paddy@2 | 89 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, device.Name, expected, result, storer) |
| paddy@2 | 90 } |
| paddy@2 | 91 } |
| paddy@10 | 92 err = factory.TeardownStorer(storer, ctx) |
| paddy@2 | 93 if err != nil { |
| paddy@2 | 94 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@2 | 95 } |
| paddy@2 | 96 } |
| paddy@2 | 97 } |
| paddy@5 | 98 |
| paddy@5 | 99 func TestGetDevicesNoErrorForMissing(t *testing.T) { |
| paddy@9 | 100 for _, factory := range storerFactories { |
| paddy@10 | 101 ctx := context.Background() |
| paddy@10 | 102 storer, err := factory.NewStorer(ctx) |
| paddy@5 | 103 if err != nil { |
| paddy@9 | 104 t.Fatalf("Fatal error creatng Storer from %T: %+v\n", factory, err) |
| paddy@5 | 105 } |
| paddy@5 | 106 |
| paddy@10 | 107 results, err := storer.GetDevices([]uuid.ID{uuid.NewID()}, ctx) |
| paddy@5 | 108 if err != nil { |
| paddy@5 | 109 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err) |
| paddy@5 | 110 } |
| paddy@5 | 111 if len(results) != 0 { |
| paddy@5 | 112 t.Errorf("Expected results to be empty, got %+v from %T instead\n", results, storer) |
| paddy@5 | 113 } |
| paddy@10 | 114 err = factory.TeardownStorer(storer, ctx) |
| paddy@5 | 115 if err != nil { |
| paddy@5 | 116 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@5 | 117 } |
| paddy@5 | 118 } |
| paddy@5 | 119 } |
| paddy@5 | 120 |
| paddy@5 | 121 func TestCreateDevicesDuplicates(t *testing.T) { |
| paddy@9 | 122 for _, factory := range storerFactories { |
| paddy@10 | 123 ctx := context.Background() |
| paddy@10 | 124 storer, err := factory.NewStorer(ctx) |
| paddy@5 | 125 if err != nil { |
| paddy@9 | 126 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@5 | 127 } |
| paddy@5 | 128 |
| paddy@5 | 129 devices := []Device{ |
| paddy@5 | 130 {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 131 {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 132 {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 133 } |
| paddy@5 | 134 |
| paddy@10 | 135 err = storer.CreateDevices(devices, ctx) |
| paddy@5 | 136 if err != nil { |
| paddy@5 | 137 t.Errorf("Unexpected error creating devices in %T: %+v\n", storer, err) |
| paddy@5 | 138 } |
| paddy@5 | 139 |
| paddy@5 | 140 newDevices := []Device{ |
| paddy@5 | 141 {ID: uuid.NewID(), Name: "Test 4", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 142 {ID: uuid.NewID(), Name: "Test 5", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 143 } |
| paddy@5 | 144 |
| paddy@10 | 145 err = storer.CreateDevices([]Device{newDevices[0], devices[1], newDevices[1]}, ctx) |
| paddy@5 | 146 daeErr, ok := err.(ErrDeviceAlreadyExists) |
| paddy@5 | 147 if !ok { |
| paddy@5 | 148 t.Errorf("Expected ErrDeviceAlreadyExists creating duplicate device in %T, got %+v\n", storer, err) |
| paddy@5 | 149 } |
| paddy@5 | 150 if !uuid.ID(daeErr).Equal(devices[1].ID) { |
| paddy@5 | 151 t.Errorf("Expected ErrDeviceAlreadyExists to be %+v, got %+v from %T\n", devices[1].ID, daeErr, storer) |
| paddy@5 | 152 } |
| paddy@5 | 153 |
| paddy@5 | 154 // inserts should be a transaction; they either all make it, or none do |
| paddy@10 | 155 results, err := storer.GetDevices([]uuid.ID{newDevices[0].ID, newDevices[1].ID}, ctx) |
| paddy@5 | 156 if err != nil { |
| paddy@5 | 157 t.Errorf("Error retrieving devices from %T: %+v\n", storer, err) |
| paddy@5 | 158 } |
| paddy@5 | 159 if len(results) != 0 { |
| paddy@5 | 160 t.Errorf("Expected new inserts to not be in results, got %+v from %T\n", results, storer) |
| paddy@5 | 161 } |
| paddy@5 | 162 |
| paddy@10 | 163 err = factory.TeardownStorer(storer, ctx) |
| paddy@5 | 164 if err != nil { |
| paddy@5 | 165 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@5 | 166 } |
| paddy@5 | 167 } |
| paddy@5 | 168 } |
| paddy@8 | 169 |
| paddy@8 | 170 func TestCreateAndListDevicesByOwner(t *testing.T) { |
| paddy@9 | 171 for _, factory := range storerFactories { |
| paddy@10 | 172 ctx := context.Background() |
| paddy@10 | 173 storer, err := factory.NewStorer(ctx) |
| paddy@8 | 174 if err != nil { |
| paddy@9 | 175 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@8 | 176 } |
| paddy@8 | 177 |
| paddy@8 | 178 owner1, owner2 := uuid.NewID(), uuid.NewID() |
| paddy@8 | 179 devices := []Device{ |
| paddy@8 | 180 {ID: uuid.NewID(), Name: "Test 1", Owner: owner1, Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@8 | 181 {ID: uuid.NewID(), Name: "Test 2", Owner: owner2, Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@8 | 182 {ID: uuid.NewID(), Name: "Test 3", Owner: owner1, Type: TypeChromeExtension, Created: time.Now().Add(time.Minute), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@8 | 183 } |
| paddy@8 | 184 |
| paddy@10 | 185 err = storer.CreateDevices(devices, ctx) |
| paddy@8 | 186 if err != nil { |
| paddy@8 | 187 t.Errorf("Error creating devices in %T: %+v\n", storer, err) |
| paddy@8 | 188 } |
| paddy@8 | 189 |
| paddy@10 | 190 results, err := storer.ListDevicesByOwner(owner1, ctx) |
| paddy@8 | 191 if err != nil { |
| paddy@8 | 192 t.Errorf("Error listing devices for owner1 from %T: %+v\n", storer, err) |
| paddy@8 | 193 } |
| paddy@8 | 194 if len(results) != 2 { |
| paddy@8 | 195 t.Errorf("Expected %d results for owner1, got %d from %T\n", 2, len(results), storer) |
| paddy@8 | 196 } |
| paddy@8 | 197 resultMap := ToMap(results) |
| paddy@8 | 198 d, ok := resultMap[devices[0].ID.String()] |
| paddy@8 | 199 if !ok { |
| paddy@8 | 200 t.Errorf("Expected to get %s in results, got %+v from %T\n", devices[0].Name, results, storer) |
| paddy@8 | 201 } |
| paddy@8 | 202 ok, field, expected, result := compareDevices(devices[0], d) |
| paddy@8 | 203 if !ok { |
| paddy@8 | 204 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[0].Name, expected, result, storer) |
| paddy@8 | 205 } |
| paddy@8 | 206 d, ok = resultMap[devices[2].ID.String()] |
| paddy@8 | 207 if !ok { |
| paddy@8 | 208 t.Errorf("Expected to get %s in results, got %+v from %T\n", devices[2].Name, results, storer) |
| paddy@8 | 209 } |
| paddy@8 | 210 ok, field, expected, result = compareDevices(devices[2], d) |
| paddy@8 | 211 if !ok { |
| paddy@8 | 212 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[2].Name, expected, result, storer) |
| paddy@8 | 213 } |
| paddy@8 | 214 |
| paddy@10 | 215 results, err = storer.ListDevicesByOwner(owner2, ctx) |
| paddy@8 | 216 if err != nil { |
| paddy@8 | 217 t.Errorf("Error listing devices for owner2 from %T: %+v\n", storer, err) |
| paddy@8 | 218 } |
| paddy@8 | 219 if len(results) != 1 { |
| paddy@8 | 220 t.Errorf("Expected %d results for owner2, got %d from %T\n", 1, len(results), storer) |
| paddy@8 | 221 } |
| paddy@8 | 222 ok, field, expected, result = compareDevices(devices[1], results[0]) |
| paddy@8 | 223 if !ok { |
| paddy@8 | 224 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[1].Name, expected, result, storer) |
| paddy@8 | 225 } |
| paddy@8 | 226 |
| paddy@10 | 227 err = factory.TeardownStorer(storer, ctx) |
| paddy@8 | 228 if err != nil { |
| paddy@8 | 229 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@8 | 230 } |
| paddy@8 | 231 } |
| paddy@8 | 232 } |
| paddy@10 | 233 |
| paddy@10 | 234 func TestUpdateDevicesHappyPath(t *testing.T) { |
| paddy@10 | 235 device := Device{ |
| paddy@10 | 236 ID: uuid.NewID(), |
| paddy@10 | 237 Name: "Test 1", |
| paddy@10 | 238 Owner: uuid.NewID(), |
| paddy@10 | 239 Type: TypeAndroidPhone, |
| paddy@10 | 240 Created: time.Now(), |
| paddy@10 | 241 LastSeen: time.Now(), |
| paddy@10 | 242 PushToken: "test token", |
| paddy@10 | 243 } |
| paddy@10 | 244 for _, factory := range storerFactories { |
| paddy@10 | 245 ctx := context.Background() |
| paddy@10 | 246 storer, err := factory.NewStorer(ctx) |
| paddy@10 | 247 if err != nil { |
| paddy@10 | 248 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@10 | 249 } |
| paddy@10 | 250 for i := 1; i < changeVariations; i++ { |
| paddy@10 | 251 var change DeviceChange |
| paddy@10 | 252 var owner uuid.ID |
| paddy@10 | 253 var name, pushToken string |
| paddy@10 | 254 var created, lastSeen time.Time |
| paddy@10 | 255 var deviceType DeviceType |
| paddy@10 | 256 |
| paddy@10 | 257 device.ID = uuid.NewID() |
| paddy@10 | 258 |
| paddy@10 | 259 expectation := device |
| paddy@10 | 260 result := device |
| paddy@10 | 261 |
| paddy@10 | 262 change.DeviceID = device.ID |
| paddy@10 | 263 |
| paddy@10 | 264 if i&changeName != 0 { |
| paddy@10 | 265 name = fmt.Sprintf("name-%d", i) |
| paddy@10 | 266 change.Name = &name |
| paddy@10 | 267 expectation.Name = name |
| paddy@10 | 268 } |
| paddy@10 | 269 if i&changeOwner != 0 { |
| paddy@10 | 270 owner = uuid.NewID() |
| paddy@10 | 271 change.Owner = &owner |
| paddy@10 | 272 expectation.Owner = owner |
| paddy@10 | 273 } |
| paddy@10 | 274 if i&changeType != 0 { |
| paddy@10 | 275 deviceType = DeviceType(fmt.Sprintf("type-%d", i)) |
| paddy@10 | 276 change.Type = &deviceType |
| paddy@10 | 277 expectation.Type = deviceType |
| paddy@10 | 278 } |
| paddy@10 | 279 if i&changeCreated != 0 { |
| paddy@10 | 280 created = time.Now().Add(time.Hour * time.Duration(i)) |
| paddy@10 | 281 change.Created = &created |
| paddy@10 | 282 expectation.Created = created |
| paddy@10 | 283 } |
| paddy@10 | 284 if i&changeLastSeen != 0 { |
| paddy@10 | 285 lastSeen = time.Now().Add(time.Minute * time.Duration(i*-1)) |
| paddy@10 | 286 change.LastSeen = &lastSeen |
| paddy@10 | 287 expectation.LastSeen = lastSeen |
| paddy@10 | 288 } |
| paddy@10 | 289 if i&changePushToken != 0 { |
| paddy@10 | 290 pushToken = fmt.Sprintf("push-token-%d", i) |
| paddy@10 | 291 change.PushToken = &pushToken |
| paddy@10 | 292 expectation.PushToken = pushToken |
| paddy@10 | 293 } |
| paddy@10 | 294 result = ApplyChange(result, change) |
| paddy@10 | 295 ok, field, expectedVal, resultVal := compareDevices(expectation, result) |
| paddy@10 | 296 if !ok { |
| paddy@10 | 297 t.Errorf("Expected %s of %s to be %v, got %v after applying DeviceChange %+v\n", field, device.Name, expectedVal, resultVal, change) |
| paddy@10 | 298 } |
| paddy@10 | 299 |
| paddy@10 | 300 err = storer.CreateDevices([]Device{device}, ctx) |
| paddy@10 | 301 if err != nil { |
| paddy@10 | 302 t.Errorf("Unexpected error creating devices in %T: %+v\n", storer, err) |
| paddy@10 | 303 } |
| paddy@10 | 304 |
| paddy@10 | 305 err = storer.UpdateDevice(change, ctx) |
| paddy@10 | 306 if err != nil { |
| paddy@10 | 307 t.Errorf("Unexpected error updating device in %T: %+v\n", storer, err) |
| paddy@10 | 308 } |
| paddy@10 | 309 |
| paddy@10 | 310 retrieved, err := storer.GetDevices([]uuid.ID{device.ID}, ctx) |
| paddy@10 | 311 if err != nil { |
| paddy@10 | 312 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err) |
| paddy@10 | 313 } |
| paddy@10 | 314 retrievedDevice, ok := retrieved[device.ID.String()] |
| paddy@10 | 315 if !ok { |
| paddy@10 | 316 t.Errorf("Expected retrieved devices to contain %s, got %+v from %T\n", device.Name, retrieved, storer) |
| paddy@10 | 317 } |
| paddy@10 | 318 ok, field, expectedVal, resultVal = compareDevices(expectation, retrievedDevice) |
| paddy@10 | 319 if !ok { |
| paddy@10 | 320 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, device.Name, expectedVal, resultVal, storer) |
| paddy@10 | 321 } |
| paddy@10 | 322 } |
| paddy@10 | 323 |
| paddy@10 | 324 err = factory.TeardownStorer(storer, ctx) |
| paddy@10 | 325 if err != nil { |
| paddy@10 | 326 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@10 | 327 } |
| paddy@10 | 328 } |
| paddy@10 | 329 } |
| paddy@11 | 330 |
| paddy@11 | 331 func TestUpdateDeviceNotFound(t *testing.T) { |
| paddy@11 | 332 for _, factory := range storerFactories { |
| paddy@11 | 333 ctx := context.Background() |
| paddy@11 | 334 storer, err := factory.NewStorer(ctx) |
| paddy@11 | 335 if err != nil { |
| paddy@11 | 336 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@11 | 337 } |
| paddy@11 | 338 |
| paddy@11 | 339 deviceID := uuid.NewID() |
| paddy@11 | 340 name := "my new name" |
| paddy@11 | 341 change := DeviceChange{DeviceID: deviceID, Name: &name} |
| paddy@11 | 342 |
| paddy@11 | 343 err = storer.UpdateDevice(change, ctx) |
| paddy@11 | 344 if err != ErrDeviceNotFound { |
| paddy@11 | 345 t.Errorf("Expected error to be ErrDeviceNotFound, %T returned %+v\n", storer, err) |
| paddy@11 | 346 } |
| paddy@11 | 347 |
| paddy@11 | 348 results, err := storer.GetDevices([]uuid.ID{deviceID}, ctx) |
| paddy@11 | 349 if err != nil { |
| paddy@11 | 350 t.Errorf("Error retrieving devices from %T: %+v\n", storer, err) |
| paddy@11 | 351 } |
| paddy@11 | 352 if len(results) != 0 { |
| paddy@11 | 353 t.Errorf("Expected no devices in %T, got %+v\n", storer, results) |
| paddy@11 | 354 } |
| paddy@11 | 355 |
| paddy@11 | 356 err = factory.TeardownStorer(storer, ctx) |
| paddy@11 | 357 if err != nil { |
| paddy@11 | 358 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@11 | 359 } |
| paddy@11 | 360 } |
| paddy@11 | 361 } |