ducky/devices
5:408abf6e48d3 Browse Files
Add more interface tests. Add a test to ensure that, when retrieving Devices, no error is returned if a Device cannot be found. Add a test to ensure that, when adding Devices, adding a Device that shares an ID with a Device already in the Storer returns an ErrDeviceAlreadyExists error. This involved creating the ErrDeviceAlreadyExists error, and modifying the in-memory implementation to properly return it. Fix a go vet issue in our previous test, wherein we forgot to pass the storer to a log message, resulting in a mismatch between the number of variables expected and the number of variables provided. Rename our tests to be better reflective of what they actually test.
devices.go memstore.go storer_test.go
1.1 --- a/devices.go Thu Nov 12 23:26:26 2015 -0800 1.2 +++ b/devices.go Sat Nov 14 05:56:04 2015 -0800 1.3 @@ -2,6 +2,7 @@ 1.4 1.5 import ( 1.6 "errors" 1.7 + "fmt" 1.8 "log" 1.9 "time" 1.10 1.11 @@ -81,6 +82,12 @@ 1.12 Destroy(c context.Context) error 1.13 } 1.14 1.15 +type ErrDeviceAlreadyExists uuid.ID 1.16 + 1.17 +func (e ErrDeviceAlreadyExists) Error() string { 1.18 + return fmt.Sprintf("device with ID %s already exists in datastore", uuid.ID(e).String()) 1.19 +} 1.20 + 1.21 // GetMany returns as many of the Devices specified by the passed IDs as possible. 1.22 // They are returned as a map, with the key being the string version of the ID. 1.23 // No error will be returned if a Device can't be found.
2.1 --- a/memstore.go Thu Nov 12 23:26:26 2015 -0800 2.2 +++ b/memstore.go Sat Nov 14 05:56:04 2015 -0800 2.3 @@ -47,6 +47,12 @@ 2.4 defer m.lock.Unlock() 2.5 2.6 for _, device := range devices { 2.7 + if _, ok := m.devices[device.ID.String()]; ok { 2.8 + return ErrDeviceAlreadyExists(device.ID) 2.9 + } 2.10 + } 2.11 + 2.12 + for _, device := range devices { 2.13 m.devices[device.ID.String()] = device 2.14 } 2.15 return nil
3.1 --- a/storer_test.go Thu Nov 12 23:26:26 2015 -0800 3.2 +++ b/storer_test.go Sat Nov 14 05:56:04 2015 -0800 3.3 @@ -35,7 +35,7 @@ 3.4 return true, "", nil, nil 3.5 } 3.6 3.7 -func TestGetDevicesHappyPath(t *testing.T) { 3.8 +func TestCreateAndGetDevices(t *testing.T) { 3.9 for _, storer := range storers { 3.10 storer, err := storer.Factory(context.TODO()) 3.11 if err != nil { 3.12 @@ -50,7 +50,7 @@ 3.13 3.14 err = storer.CreateDevices(devices, context.TODO()) 3.15 if err != nil { 3.16 - t.Errorf("Error creating devices in %T: %+v\n", err) 3.17 + t.Errorf("Error creating devices in %T: %+v\n", storer, err) 3.18 } 3.19 3.20 ids := make([]uuid.ID, 0, len(devices)) 3.21 @@ -78,3 +78,72 @@ 3.22 } 3.23 } 3.24 } 3.25 + 3.26 +func TestGetDevicesNoErrorForMissing(t *testing.T) { 3.27 + for _, storer := range storers { 3.28 + storer, err := storer.Factory(context.TODO()) 3.29 + if err != nil { 3.30 + t.Fatalf("Fatal error creatng %T: %+v\n", storer, err) 3.31 + } 3.32 + 3.33 + results, err := storer.GetDevices([]uuid.ID{uuid.NewID()}, context.TODO()) 3.34 + if err != nil { 3.35 + t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err) 3.36 + } 3.37 + if len(results) != 0 { 3.38 + t.Errorf("Expected results to be empty, got %+v from %T instead\n", results, storer) 3.39 + } 3.40 + err = storer.Destroy(context.TODO()) 3.41 + if err != nil { 3.42 + t.Errorf("Error cleaning up after %T: %+v\n", storer, err) 3.43 + } 3.44 + } 3.45 +} 3.46 + 3.47 +func TestCreateDevicesDuplicates(t *testing.T) { 3.48 + for _, storer := range storers { 3.49 + storer, err := storer.Factory(context.TODO()) 3.50 + if err != nil { 3.51 + t.Fatalf("Fatal error creating %T: %+v\n", storer, err) 3.52 + } 3.53 + 3.54 + devices := []Device{ 3.55 + {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, 3.56 + {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, 3.57 + {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, 3.58 + } 3.59 + 3.60 + err = storer.CreateDevices(devices, context.TODO()) 3.61 + if err != nil { 3.62 + t.Errorf("Unexpected error creating devices in %T: %+v\n", storer, err) 3.63 + } 3.64 + 3.65 + newDevices := []Device{ 3.66 + {ID: uuid.NewID(), Name: "Test 4", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, 3.67 + {ID: uuid.NewID(), Name: "Test 5", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, 3.68 + } 3.69 + 3.70 + err = storer.CreateDevices([]Device{newDevices[0], devices[1], newDevices[1]}, context.TODO()) 3.71 + daeErr, ok := err.(ErrDeviceAlreadyExists) 3.72 + if !ok { 3.73 + t.Errorf("Expected ErrDeviceAlreadyExists creating duplicate device in %T, got %+v\n", storer, err) 3.74 + } 3.75 + if !uuid.ID(daeErr).Equal(devices[1].ID) { 3.76 + t.Errorf("Expected ErrDeviceAlreadyExists to be %+v, got %+v from %T\n", devices[1].ID, daeErr, storer) 3.77 + } 3.78 + 3.79 + // inserts should be a transaction; they either all make it, or none do 3.80 + results, err := storer.GetDevices([]uuid.ID{newDevices[0].ID, newDevices[1].ID}, context.TODO()) 3.81 + if err != nil { 3.82 + t.Errorf("Error retrieving devices from %T: %+v\n", storer, err) 3.83 + } 3.84 + if len(results) != 0 { 3.85 + t.Errorf("Expected new inserts to not be in results, got %+v from %T\n", results, storer) 3.86 + } 3.87 + 3.88 + err = storer.Destroy(context.TODO()) 3.89 + if err != nil { 3.90 + t.Errorf("Error cleaning up after %T: %+v\n", storer, err) 3.91 + } 3.92 + } 3.93 +}