ducky/devices
ducky/devices/storer_test.go
Separate out StorerFactory into its own interface. We had previously included a Factory and Destroy method in the Storer interface, but it makes more sense to separate those out into a StorerFactory interface. This lets us avoid the impression that we're using the same instance of a Storer for every test, separates the test methods away from the methods that are actually used in prod, and hides them from Godoc. What's not to like?
| paddy@2 | 1 package devices |
| paddy@2 | 2 |
| paddy@2 | 3 import ( |
| paddy@2 | 4 "testing" |
| paddy@2 | 5 "time" |
| paddy@2 | 6 |
| paddy@2 | 7 "code.secondbit.org/uuid.hg" |
| paddy@2 | 8 "golang.org/x/net/context" |
| paddy@2 | 9 ) |
| paddy@2 | 10 |
| paddy@9 | 11 type StorerFactory interface { |
| paddy@9 | 12 NewStorer(ctx context.Context) (Storer, error) |
| paddy@9 | 13 TeardownStorer(storer Storer, ctx context.Context) error |
| paddy@9 | 14 } |
| paddy@9 | 15 |
| paddy@9 | 16 var storerFactories []StorerFactory |
| paddy@2 | 17 |
| paddy@2 | 18 func compareDevices(device1, device2 Device) (ok bool, field string, expected, result interface{}) { |
| paddy@2 | 19 if !device1.ID.Equal(device2.ID) { |
| paddy@2 | 20 return false, "ID", device1.ID, device2.ID |
| paddy@2 | 21 } |
| paddy@2 | 22 if device1.Name != device2.Name { |
| paddy@2 | 23 return false, "Name", device1.Name, device2.Name |
| paddy@2 | 24 } |
| paddy@2 | 25 if !device1.Owner.Equal(device2.Owner) { |
| paddy@2 | 26 return false, "Owner", device1.Owner, device2.Owner |
| paddy@2 | 27 } |
| paddy@2 | 28 if device1.Type != device2.Type { |
| paddy@2 | 29 return false, "Type", device1.Type, device2.Type |
| paddy@2 | 30 } |
| paddy@2 | 31 if !device1.Created.Equal(device2.Created) { |
| paddy@2 | 32 return false, "Created", device1.Created, device2.Created |
| paddy@2 | 33 } |
| paddy@2 | 34 if !device1.LastSeen.Equal(device2.LastSeen) { |
| paddy@2 | 35 return false, "LastSeen", device1.LastSeen, device2.LastSeen |
| paddy@2 | 36 } |
| paddy@2 | 37 if device1.PushToken != device2.PushToken { |
| paddy@2 | 38 return false, "PushToken", device1.PushToken, device2.PushToken |
| paddy@2 | 39 } |
| paddy@2 | 40 return true, "", nil, nil |
| paddy@2 | 41 } |
| paddy@2 | 42 |
| paddy@5 | 43 func TestCreateAndGetDevices(t *testing.T) { |
| paddy@9 | 44 for _, factory := range storerFactories { |
| paddy@9 | 45 storer, err := factory.NewStorer(context.TODO()) |
| paddy@2 | 46 if err != nil { |
| paddy@9 | 47 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@2 | 48 } |
| paddy@2 | 49 |
| paddy@2 | 50 devices := []Device{ |
| paddy@2 | 51 {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@2 | 52 {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@2 | 53 {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@2 | 54 } |
| paddy@3 | 55 |
| paddy@3 | 56 err = storer.CreateDevices(devices, context.TODO()) |
| paddy@3 | 57 if err != nil { |
| paddy@5 | 58 t.Errorf("Error creating devices in %T: %+v\n", storer, err) |
| paddy@3 | 59 } |
| paddy@3 | 60 |
| paddy@2 | 61 ids := make([]uuid.ID, 0, len(devices)) |
| paddy@2 | 62 for _, device := range devices { |
| paddy@2 | 63 ids = append(ids, device.ID) |
| paddy@2 | 64 } |
| paddy@2 | 65 |
| paddy@2 | 66 results, err := storer.GetDevices(ids, context.TODO()) |
| paddy@2 | 67 if err != nil { |
| paddy@2 | 68 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err) |
| paddy@2 | 69 } |
| paddy@2 | 70 for _, device := range devices { |
| paddy@2 | 71 d, returned := results[device.ID.String()] |
| paddy@2 | 72 if !returned { |
| paddy@2 | 73 t.Errorf("Expected device %s to be in results from %T, but wasn't present\n", device.Name, storer) |
| paddy@2 | 74 } |
| paddy@2 | 75 ok, field, expected, result := compareDevices(device, d) |
| paddy@2 | 76 if !ok { |
| paddy@2 | 77 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, device.Name, expected, result, storer) |
| paddy@2 | 78 } |
| paddy@2 | 79 } |
| paddy@9 | 80 err = factory.TeardownStorer(storer, context.TODO()) |
| paddy@2 | 81 if err != nil { |
| paddy@2 | 82 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@2 | 83 } |
| paddy@2 | 84 } |
| paddy@2 | 85 } |
| paddy@5 | 86 |
| paddy@5 | 87 func TestGetDevicesNoErrorForMissing(t *testing.T) { |
| paddy@9 | 88 for _, factory := range storerFactories { |
| paddy@9 | 89 storer, err := factory.NewStorer(context.TODO()) |
| paddy@5 | 90 if err != nil { |
| paddy@9 | 91 t.Fatalf("Fatal error creatng Storer from %T: %+v\n", factory, err) |
| paddy@5 | 92 } |
| paddy@5 | 93 |
| paddy@5 | 94 results, err := storer.GetDevices([]uuid.ID{uuid.NewID()}, context.TODO()) |
| paddy@5 | 95 if err != nil { |
| paddy@5 | 96 t.Errorf("Unexpected error retrieving devices from %T: %+v\n", storer, err) |
| paddy@5 | 97 } |
| paddy@5 | 98 if len(results) != 0 { |
| paddy@5 | 99 t.Errorf("Expected results to be empty, got %+v from %T instead\n", results, storer) |
| paddy@5 | 100 } |
| paddy@9 | 101 err = factory.TeardownStorer(storer, context.TODO()) |
| paddy@5 | 102 if err != nil { |
| paddy@5 | 103 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@5 | 104 } |
| paddy@5 | 105 } |
| paddy@5 | 106 } |
| paddy@5 | 107 |
| paddy@5 | 108 func TestCreateDevicesDuplicates(t *testing.T) { |
| paddy@9 | 109 for _, factory := range storerFactories { |
| paddy@9 | 110 storer, err := factory.NewStorer(context.TODO()) |
| paddy@5 | 111 if err != nil { |
| paddy@9 | 112 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@5 | 113 } |
| paddy@5 | 114 |
| paddy@5 | 115 devices := []Device{ |
| paddy@5 | 116 {ID: uuid.NewID(), Name: "Test 1", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 117 {ID: uuid.NewID(), Name: "Test 2", Owner: uuid.NewID(), Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 118 {ID: uuid.NewID(), Name: "Test 3", Owner: uuid.NewID(), Type: TypeChromeExtension, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 119 } |
| paddy@5 | 120 |
| paddy@5 | 121 err = storer.CreateDevices(devices, context.TODO()) |
| paddy@5 | 122 if err != nil { |
| paddy@5 | 123 t.Errorf("Unexpected error creating devices in %T: %+v\n", storer, err) |
| paddy@5 | 124 } |
| paddy@5 | 125 |
| paddy@5 | 126 newDevices := []Device{ |
| paddy@5 | 127 {ID: uuid.NewID(), Name: "Test 4", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 128 {ID: uuid.NewID(), Name: "Test 5", Owner: uuid.NewID(), Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@5 | 129 } |
| paddy@5 | 130 |
| paddy@5 | 131 err = storer.CreateDevices([]Device{newDevices[0], devices[1], newDevices[1]}, context.TODO()) |
| paddy@5 | 132 daeErr, ok := err.(ErrDeviceAlreadyExists) |
| paddy@5 | 133 if !ok { |
| paddy@5 | 134 t.Errorf("Expected ErrDeviceAlreadyExists creating duplicate device in %T, got %+v\n", storer, err) |
| paddy@5 | 135 } |
| paddy@5 | 136 if !uuid.ID(daeErr).Equal(devices[1].ID) { |
| paddy@5 | 137 t.Errorf("Expected ErrDeviceAlreadyExists to be %+v, got %+v from %T\n", devices[1].ID, daeErr, storer) |
| paddy@5 | 138 } |
| paddy@5 | 139 |
| paddy@5 | 140 // inserts should be a transaction; they either all make it, or none do |
| paddy@5 | 141 results, err := storer.GetDevices([]uuid.ID{newDevices[0].ID, newDevices[1].ID}, context.TODO()) |
| paddy@5 | 142 if err != nil { |
| paddy@5 | 143 t.Errorf("Error retrieving devices from %T: %+v\n", storer, err) |
| paddy@5 | 144 } |
| paddy@5 | 145 if len(results) != 0 { |
| paddy@5 | 146 t.Errorf("Expected new inserts to not be in results, got %+v from %T\n", results, storer) |
| paddy@5 | 147 } |
| paddy@5 | 148 |
| paddy@9 | 149 err = factory.TeardownStorer(storer, context.TODO()) |
| paddy@5 | 150 if err != nil { |
| paddy@5 | 151 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@5 | 152 } |
| paddy@5 | 153 } |
| paddy@5 | 154 } |
| paddy@8 | 155 |
| paddy@8 | 156 func TestCreateAndListDevicesByOwner(t *testing.T) { |
| paddy@9 | 157 for _, factory := range storerFactories { |
| paddy@9 | 158 storer, err := factory.NewStorer(context.TODO()) |
| paddy@8 | 159 if err != nil { |
| paddy@9 | 160 t.Fatalf("Fatal error creating Storer from %T: %+v\n", factory, err) |
| paddy@8 | 161 } |
| paddy@8 | 162 |
| paddy@8 | 163 owner1, owner2 := uuid.NewID(), uuid.NewID() |
| paddy@8 | 164 devices := []Device{ |
| paddy@8 | 165 {ID: uuid.NewID(), Name: "Test 1", Owner: owner1, Type: TypeAndroidPhone, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@8 | 166 {ID: uuid.NewID(), Name: "Test 2", Owner: owner2, Type: TypeAndroidTablet, Created: time.Now(), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@8 | 167 {ID: uuid.NewID(), Name: "Test 3", Owner: owner1, Type: TypeChromeExtension, Created: time.Now().Add(time.Minute), LastSeen: time.Now(), PushToken: "test token"}, |
| paddy@8 | 168 } |
| paddy@8 | 169 |
| paddy@8 | 170 err = storer.CreateDevices(devices, context.TODO()) |
| paddy@8 | 171 if err != nil { |
| paddy@8 | 172 t.Errorf("Error creating devices in %T: %+v\n", storer, err) |
| paddy@8 | 173 } |
| paddy@8 | 174 |
| paddy@8 | 175 results, err := storer.ListDevicesByOwner(owner1, context.TODO()) |
| paddy@8 | 176 if err != nil { |
| paddy@8 | 177 t.Errorf("Error listing devices for owner1 from %T: %+v\n", storer, err) |
| paddy@8 | 178 } |
| paddy@8 | 179 if len(results) != 2 { |
| paddy@8 | 180 t.Errorf("Expected %d results for owner1, got %d from %T\n", 2, len(results), storer) |
| paddy@8 | 181 } |
| paddy@8 | 182 resultMap := ToMap(results) |
| paddy@8 | 183 d, ok := resultMap[devices[0].ID.String()] |
| paddy@8 | 184 if !ok { |
| paddy@8 | 185 t.Errorf("Expected to get %s in results, got %+v from %T\n", devices[0].Name, results, storer) |
| paddy@8 | 186 } |
| paddy@8 | 187 ok, field, expected, result := compareDevices(devices[0], d) |
| paddy@8 | 188 if !ok { |
| paddy@8 | 189 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[0].Name, expected, result, storer) |
| paddy@8 | 190 } |
| paddy@8 | 191 d, ok = resultMap[devices[2].ID.String()] |
| paddy@8 | 192 if !ok { |
| paddy@8 | 193 t.Errorf("Expected to get %s in results, got %+v from %T\n", devices[2].Name, results, storer) |
| paddy@8 | 194 } |
| paddy@8 | 195 ok, field, expected, result = compareDevices(devices[2], d) |
| paddy@8 | 196 if !ok { |
| paddy@8 | 197 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[2].Name, expected, result, storer) |
| paddy@8 | 198 } |
| paddy@8 | 199 |
| paddy@8 | 200 results, err = storer.ListDevicesByOwner(owner2, context.TODO()) |
| paddy@8 | 201 if err != nil { |
| paddy@8 | 202 t.Errorf("Error listing devices for owner2 from %T: %+v\n", storer, err) |
| paddy@8 | 203 } |
| paddy@8 | 204 if len(results) != 1 { |
| paddy@8 | 205 t.Errorf("Expected %d results for owner2, got %d from %T\n", 1, len(results), storer) |
| paddy@8 | 206 } |
| paddy@8 | 207 ok, field, expected, result = compareDevices(devices[1], results[0]) |
| paddy@8 | 208 if !ok { |
| paddy@8 | 209 t.Errorf("Expected %s of %s to be %v, got %v from %T\n", field, devices[1].Name, expected, result, storer) |
| paddy@8 | 210 } |
| paddy@8 | 211 |
| paddy@9 | 212 err = factory.TeardownStorer(storer, context.TODO()) |
| paddy@8 | 213 if err != nil { |
| paddy@8 | 214 t.Errorf("Error cleaning up after %T: %+v\n", storer, err) |
| paddy@8 | 215 } |
| paddy@8 | 216 } |
| paddy@8 | 217 } |