ducky/devices
ducky/devices/storer_test.go
Implement and test updating devices, and reuse contexts. Update all our tests to use the same context.Context instance within each test case, so static analysis about how we're passing contexts around dosn't get tripped up. Also, write a test that will check to make sure that our Storer implementations all actually update the Device correctly. We create every possible permutation of a DeviceChange, just to make sure they all work.
| 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 } |