Validate device creation.
Update our uuid package to the latest, which is now based on the GitHub
fork instead of the Google Code. Also, update our api package to its latest
version, which now needs the pqarrays package as a dependency.
We fleshed out the validateDeviceCreation. We now pass in the scopes we have
(for broad access control) and the user ID (for fine-grained access control).
This helper returns the first error it encounters, though it should probably
return a slice so we can return multiple errors all at once.
Before we even decode the request to create a Device, let's check if the user is
even logged in. If we can't ascertain that or they're not, there's no point in
even consuming the memory necessary to read the request, because we know we're
not going to use it anyways.
Finally actually validate the devices we're creating, and return an appropriate
error for each error we can get.
Also, the api.CheckScopes helper function now takes the scopes passed in as a
string slice, and we have an api.GetScopes helper function to retrieve the
scopes associated with the request. Let's not keep parsing that.
We need two new scopes to control access for device creation; ScopeImport lets
users import devices in and is pretty much admin access.
ScopeCreateOtherUserDevices allows a user to create Devices that are owned by
another user.
8 "code.secondbit.org/uuid.hg"
9 "golang.org/x/net/context"
12 type StorerFactory interface {
13 NewStorer(ctx context.Context) (Storer, error)
14 TeardownStorer(storer Storer, ctx context.Context) error
17 var storerFactories []StorerFactory
20 changeName = 1 << iota
28 func compareDevices(device1, device2 Device) (ok bool, field string, expected, result interface{}) {
29 if !device1.ID.Equal(device2.ID) {
30 return false, "ID", device1.ID, device2.ID
32 if device1.Name != device2.Name {
33 return false, "Name", device1.Name, device2.Name
35 if !device1.Owner.Equal(device2.Owner) {
36 return false, "Owner", device1.Owner, device2.Owner
38 if device1.Type != device2.Type {
39 return false, "Type", device1.Type, device2.Type
41 if !device1.Created.Equal(device2.Created) {
42 return false, "Created", device1.Created, device2.Created
44 if !device1.LastSeen.Equal(device2.LastSeen) {
45 return false, "LastSeen", device1.LastSeen, device2.LastSeen
47 if device1.PushToken != device2.PushToken {
48 return false, "PushToken", device1.PushToken, device2.PushToken
50 return true, "", nil, nil
53 func TestCreateAndGetDevices(t *testing.T) {
54 for _, factory := range storerFactories {
55 ctx := context.Background()
56 storer, err := factory.NewStorer(ctx)
58 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err)
62 {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
63 {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
64 {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
67 err = storer.CreateDevices(devices, ctx)
69 t.Errorf("Error creating devices in %T: %+v\n", storer, err)
72 ids := make([]uuid.ID, 0, len(devices))
73 for _, device := range devices {
74 ids = append(ids, device.ID)
77 results, err := storer.GetDevices(ids, ctx)
79 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err)
81 for _, device := range devices {
82 d, returned := results[device.ID.String()]
84 t.Errorf("Expected device %s to be in results from %T, but wasn't present\n", device.Name, storer)
86 ok, field, expected, result := compareDevices(device, d)
88 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, device.Name, expected, result, storer)
91 err = factory.TeardownStorer(storer, ctx)
93 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)
98 func TestGetDevicesNoErrorForMissing(t *testing.T) {
99 for _, factory := range storerFactories {
100 ctx := context.Background()
101 storer, err := factory.NewStorer(ctx)
103 t.Fatalf("Fatal error creatng Storer from %T: %+v\n", factory, err)
106 results, err := storer.GetDevices([]uuid.ID{uuid.NewID()}, ctx)
108 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err)
110 if len(results) != 0 {
111 t.Errorf("Expected results to be empty, got %+v from %T instead\n", results, storer)
113 err = factory.TeardownStorer(storer, ctx)
115 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)
120 func TestCreateDevicesDuplicates(t *testing.T) {
121 for _, factory := range storerFactories {
122 ctx := context.Background()
123 storer, err := factory.NewStorer(ctx)
125 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err)
129 {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
130 {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
131 {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
134 err = storer.CreateDevices(devices, ctx)
136 t.Errorf("Unexpected error creating devices in %T: %+v\n", storer, err)
139 newDevices := []Device{
140 {ID: uuid.NewID(), Name: "Test 4", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
141 {ID: uuid.NewID(), Name: "Test 5", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
144 err = storer.CreateDevices([]Device{newDevices[0], devices[1], newDevices[1]}, ctx)
145 daeErr, ok := err.(ErrDeviceAlreadyExists)
147 t.Errorf("Expected ErrDeviceAlreadyExists creating duplicate device in %T, got %+v\n", storer, err)
149 if !uuid.ID(daeErr).Equal(devices[1].ID) {
150 t.Errorf("Expected ErrDeviceAlreadyExists to be %+v, got %+v from %T\n", devices[1].ID, daeErr, storer)
153 // inserts should be a transaction; they either all make it, or none do
154 results, err := storer.GetDevices([]uuid.ID{newDevices[0].ID, newDevices[1].ID}, ctx)
156 t.Errorf("Error retrieving devices from %T: %+v\n", storer, err)
158 if len(results) != 0 {
159 t.Errorf("Expected new inserts to not be in results, got %+v from %T\n", results, storer)
162 err = factory.TeardownStorer(storer, ctx)
164 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)
169 func TestCreateAndListDevicesByOwner(t *testing.T) {
170 for _, factory := range storerFactories {
171 ctx := context.Background()
172 storer, err := factory.NewStorer(ctx)
174 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err)
177 owner1, owner2 := uuid.NewID(), uuid.NewID()
179 {ID: uuid.NewID(), Name: "Test 1", Owner: owner1, Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
180 {ID: uuid.NewID(), Name: "Test 2", Owner: owner2, Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
181 {ID: uuid.NewID(), Name: "Test 3", Owner: owner1, Type: TypeChromeExtension, Created: time.Now().Add(time.Minute), LastSeen: time.Now(), PushToken: "test token"},
184 err = storer.CreateDevices(devices, ctx)
186 t.Errorf("Error creating devices in %T: %+v\n", storer, err)
189 results, err := storer.ListDevicesByOwner(owner1, ctx)
191 t.Errorf("Error listing devices for owner1 from %T: %+v\n", storer, err)
193 if len(results) != 2 {
194 t.Errorf("Expected %d results for owner1, got %d from %T\n", 2, len(results), storer)
196 resultMap := ToMap(results)
197 d, ok := resultMap[devices[0].ID.String()]
199 t.Errorf("Expected to get %s in results, got %+v from %T\n", devices[0].Name, results, storer)
201 ok, field, expected, result := compareDevices(devices[0], d)
203 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[0].Name, expected, result, storer)
205 d, ok = resultMap[devices[2].ID.String()]
207 t.Errorf("Expected to get %s in results, got %+v from %T\n", devices[2].Name, results, storer)
209 ok, field, expected, result = compareDevices(devices[2], d)
211 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[2].Name, expected, result, storer)
214 results, err = storer.ListDevicesByOwner(owner2, ctx)
216 t.Errorf("Error listing devices for owner2 from %T: %+v\n", storer, err)
218 if len(results) != 1 {
219 t.Errorf("Expected %d results for owner2, got %d from %T\n", 1, len(results), storer)
221 ok, field, expected, result = compareDevices(devices[1], results[0])
223 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[1].Name, expected, result, storer)
226 err = factory.TeardownStorer(storer, ctx)
228 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)
233 func TestUpdateDevicesHappyPath(t *testing.T) {
238 Type: TypeAndroidPhone,
240 LastSeen: time.Now(),
241 PushToken: "test token",
243 for _, factory := range storerFactories {
244 ctx := context.Background()
245 storer, err := factory.NewStorer(ctx)
247 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err)
249 for i := 1; i < changeVariations; i++ {
250 var change DeviceChange
252 var name, pushToken string
253 var lastSeen time.Time
254 var deviceType DeviceType
256 device.ID = uuid.NewID()
258 expectation := device
261 change.DeviceID = device.ID
263 if i&changeName != 0 {
264 name = fmt.Sprintf("name-%d", i)
266 expectation.Name = name
268 if i&changeOwner != 0 {
270 change.Owner = &owner
271 expectation.Owner = owner
273 if i&changeType != 0 {
274 deviceType = DeviceType(fmt.Sprintf("type-%d", i))
275 change.Type = &deviceType
276 expectation.Type = deviceType
278 if i&changeLastSeen != 0 {
279 lastSeen = time.Now().Add(time.Minute * time.Duration(i*-1))
280 change.LastSeen = &lastSeen
281 expectation.LastSeen = lastSeen
283 if i&changePushToken != 0 {
284 pushToken = fmt.Sprintf("push-token-%d", i)
285 change.PushToken = &pushToken
286 expectation.PushToken = pushToken
288 result = ApplyChange(result, change)
289 ok, field, expectedVal, resultVal := compareDevices(expectation, result)
291 t.Errorf("Expected %s of %s to be %v, got %v after applying DeviceChange %+v\n", field, device.Name, expectedVal, resultVal, change)
294 err = storer.CreateDevices([]Device{device}, ctx)
296 t.Errorf("Unexpected error creating devices in %T: %+v\n", storer, err)
299 err = storer.UpdateDevice(change, ctx)
301 t.Errorf("Unexpected error updating device in %T: %+v\n", storer, err)
304 retrieved, err := storer.GetDevices([]uuid.ID{device.ID}, ctx)
306 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err)
308 retrievedDevice, ok := retrieved[device.ID.String()]
310 t.Errorf("Expected retrieved devices to contain %s, got %+v from %T\n", device.Name, retrieved, storer)
312 ok, field, expectedVal, resultVal = compareDevices(expectation, retrievedDevice)
314 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, device.Name, expectedVal, resultVal, storer)
318 err = factory.TeardownStorer(storer, ctx)
320 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)
325 func TestUpdateDeviceNotFound(t *testing.T) {
326 for _, factory := range storerFactories {
327 ctx := context.Background()
328 storer, err := factory.NewStorer(ctx)
330 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err)
333 deviceID := uuid.NewID()
334 name := "my new name"
335 change := DeviceChange{DeviceID: deviceID, Name: &name}
337 err = storer.UpdateDevice(change, ctx)
338 if err != ErrDeviceNotFound {
339 t.Errorf("Expected error to be ErrDeviceNotFound, %T returned %+v\n", storer, err)
342 results, err := storer.GetDevices([]uuid.ID{deviceID}, ctx)
344 t.Errorf("Error retrieving devices from %T: %+v\n", storer, err)
346 if len(results) != 0 {
347 t.Errorf("Expected no devices in %T, got %+v\n", storer, results)
350 err = factory.TeardownStorer(storer, ctx)
352 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)
357 func TestDeleteDevicesHappyPath(t *testing.T) {
358 for _, factory := range storerFactories {
359 ctx := context.Background()
360 storer, err := factory.NewStorer(ctx)
362 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err)
365 owner1, owner2 := uuid.NewID(), uuid.NewID()
368 {ID: uuid.NewID(), Name: "Test 1", Owner: owner1, Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
369 {ID: uuid.NewID(), Name: "Test 2", Owner: owner2, Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
370 {ID: uuid.NewID(), Name: "Test 3", Owner: owner1, Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"},
373 err = storer.CreateDevices(devices, ctx)
375 t.Errorf("Error creating devices in %T: %+v\n", storer, err)
378 err = storer.DeleteDevices([]uuid.ID{devices[0].ID, devices[1].ID}, ctx)
380 t.Errorf("Error deleting devices from %T: %+v\n", storer, err)
383 results, err := storer.GetDevices([]uuid.ID{devices[0].ID, devices[1].ID, devices[2].ID}, ctx)
385 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err)
388 if len(results) != 1 {
389 t.Errorf("Expected %d results, got %d from %T\n", 1, len(results), storer)
392 device, ok := results[devices[0].ID.String()]
394 t.Errorf("Retrieved first device (which was deleted!) from %T: %+v\n", storer, device)
397 device, ok = results[devices[1].ID.String()]
399 t.Errorf("Retrieved second device (which was deleted!) from %T: %+v\n", storer, device)
402 device, ok = results[devices[2].ID.String()]
404 t.Errorf("Didn't retrieve third device (which wasn't deleted!) from %T. Got %+v\n", storer, results)
407 err = factory.TeardownStorer(storer, ctx)
409 t.Errorf("Error cleaning up after %T: %+v\n", storer, err)