ducky/devices

Paddy 2015-11-27 Parent:f5a9d5f8f28d Child:683050b4546b

10:74dbc04879a7 Go to Latest

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.

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