ducky/devices

Paddy 2015-11-29 Parent:03c49b4d3d9f Child:c24a6c5fcd8c

13:e3ced527d4ab Go to Latest

ducky/devices/devices.go

Add tests for ToSlice and ToMap. Add simple unit tests that verify that ToSlice and ToMap both work as we expect. These are probably overly-simplistic, but so are the functions we're testing. Not sure about this... feel a bit conflicted. But let's try it.

History
paddy@0 1 package devices
paddy@0 2
paddy@0 3 import (
paddy@0 4 "errors"
paddy@5 5 "fmt"
paddy@0 6 "log"
paddy@0 7 "time"
paddy@0 8
paddy@0 9 "golang.org/x/net/context"
paddy@0 10
paddy@0 11 "code.secondbit.org/uuid.hg"
paddy@0 12 )
paddy@0 13
paddy@0 14 var (
paddy@0 15 // ErrDeviceNotFound is returned when the specified device couldn't be found.
paddy@0 16 ErrDeviceNotFound = errors.New("device not found")
paddy@0 17 )
paddy@0 18
paddy@0 19 // Device represents a specific device that updates can be pushed to.
paddy@0 20 type Device struct {
paddy@0 21 ID uuid.ID
paddy@0 22 Name string
paddy@0 23 Owner uuid.ID
paddy@0 24 Type DeviceType
paddy@0 25 Created time.Time
paddy@0 26 LastSeen time.Time
paddy@0 27 PushToken string
paddy@0 28 }
paddy@0 29
paddy@0 30 // ApplyChange returns a Device that is a copy of the passed Device,
paddy@0 31 // but with the passed DeviceChange applied.
paddy@0 32 func ApplyChange(d Device, change DeviceChange) Device {
paddy@0 33 result := d
paddy@0 34 if change.Name != nil {
paddy@0 35 result.Name = *change.Name
paddy@0 36 }
paddy@0 37 if change.Owner != nil {
paddy@0 38 result.Owner = *change.Owner
paddy@0 39 } else {
paddy@0 40 // We don't want to accidentally leave a slice that
paddy@0 41 // is owned by both behind.
paddy@0 42 result.Owner = d.Owner.Copy()
paddy@0 43 }
paddy@0 44 if change.Type != nil {
paddy@0 45 result.Type = *change.Type
paddy@0 46 }
paddy@0 47 if change.Created != nil {
paddy@0 48 result.Created = *change.Created
paddy@0 49 }
paddy@0 50 if change.LastSeen != nil {
paddy@0 51 result.LastSeen = *change.LastSeen
paddy@0 52 }
paddy@0 53 if change.PushToken != nil {
paddy@0 54 result.PushToken = *change.PushToken
paddy@0 55 }
paddy@0 56 return result
paddy@0 57 }
paddy@0 58
paddy@0 59 // DeviceChange represents a set of changes to a Device that will be used
paddy@0 60 // to update a Device.
paddy@0 61 type DeviceChange struct {
paddy@0 62 DeviceID uuid.ID
paddy@0 63 Name *string
paddy@0 64 Owner *uuid.ID
paddy@0 65 Type *DeviceType
paddy@0 66 Created *time.Time
paddy@0 67 LastSeen *time.Time
paddy@0 68 PushToken *string
paddy@0 69 }
paddy@0 70
paddy@0 71 // Storer is an interface to control how data is stored in and retrieved from
paddy@0 72 // the datastore.
paddy@0 73 type Storer interface {
paddy@0 74 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error)
paddy@0 75 UpdateDevice(change DeviceChange, c context.Context) error
paddy@0 76 DeleteDevices(ids []uuid.ID, c context.Context) error
paddy@0 77 CreateDevices(devices []Device, c context.Context) error
paddy@0 78 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error)
paddy@0 79 }
paddy@0 80
paddy@12 81 // ErrDeviceAlreadyExists is returned when a Device is added to a Storer, but a
paddy@12 82 // Device with the same ID already exists in the Storer. The ID underlying
paddy@12 83 // ErrDeviceAlreadyExists will hold the ID that already exists.
paddy@5 84 type ErrDeviceAlreadyExists uuid.ID
paddy@5 85
paddy@5 86 func (e ErrDeviceAlreadyExists) Error() string {
paddy@5 87 return fmt.Sprintf("device with ID %s already exists in datastore", uuid.ID(e).String())
paddy@5 88 }
paddy@5 89
paddy@0 90 // GetMany returns as many of the Devices specified by the passed IDs as possible.
paddy@0 91 // They are returned as a map, with the key being the string version of the ID.
paddy@0 92 // No error will be returned if a Device can't be found.
paddy@0 93 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
paddy@0 94 results := map[string]Device{}
paddy@0 95 storer, err := getStorer(c)
paddy@0 96 if err != nil {
paddy@0 97 log.Printf("Error retrieving Storer: %+v\n", err)
paddy@0 98 return results, err
paddy@0 99 }
paddy@0 100 results, err = storer.GetDevices(ids, c)
paddy@0 101 if err != nil {
paddy@0 102 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err)
paddy@0 103 return results, err
paddy@0 104 }
paddy@0 105 return results, nil
paddy@0 106 }
paddy@0 107
paddy@0 108 // Get returns the Device specified by the passed ID. If the Device can't be found,
paddy@0 109 // an ErrDeviceNotFound error is returned.
paddy@0 110 func Get(id uuid.ID, c context.Context) (Device, error) {
paddy@0 111 results, err := GetMany([]uuid.ID{id}, c)
paddy@0 112 if err != nil {
paddy@0 113 return Device{}, err
paddy@0 114 }
paddy@0 115 result, ok := results[id.String()]
paddy@0 116 if !ok {
paddy@0 117 return Device{}, ErrDeviceNotFound
paddy@0 118 }
paddy@0 119 return result, nil
paddy@0 120 }
paddy@0 121
paddy@0 122 // Update applies the DeviceChange to the passed Device, and returns the result. If
paddy@0 123 // the Device can't be found, an ErrDeviceNotFound error was returned.
paddy@0 124 func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
paddy@0 125 storer, err := getStorer(c)
paddy@0 126 if err != nil {
paddy@0 127 log.Printf("Error retrieving Storer: %+v\n", err)
paddy@0 128 return Device{}, err
paddy@0 129 }
paddy@0 130 change.DeviceID = device.ID
paddy@0 131 err = storer.UpdateDevice(change, c)
paddy@0 132 if err != nil {
paddy@0 133 return Device{}, err
paddy@0 134 }
paddy@0 135 return ApplyChange(device, change), nil
paddy@0 136 }
paddy@0 137
paddy@0 138 // DeleteMany removes the passed IDs from the datastore. No error is returned if the
paddy@0 139 // ID doesn't correspond to a Device in the datastore.
paddy@0 140 func DeleteMany(ids []uuid.ID, c context.Context) error {
paddy@0 141 storer, err := getStorer(c)
paddy@0 142 if err != nil {
paddy@0 143 log.Printf("Error retrieving Storer: %+v\n", err)
paddy@0 144 return err
paddy@0 145 }
paddy@0 146 return storer.DeleteDevices(ids, c)
paddy@0 147 }
paddy@0 148
paddy@0 149 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't
paddy@0 150 // correspond to a Device in the datastore.
paddy@0 151 func Delete(id uuid.ID, c context.Context) error {
paddy@0 152 return DeleteMany([]uuid.ID{id}, c)
paddy@0 153 }
paddy@0 154
paddy@0 155 // CreateMany stores the passed Devices in the datastore, assigning default values if
paddy@0 156 // necessary. The Devices that were ultimately stored (including any default values, if
paddy@0 157 // applicable) are returned.
paddy@0 158 func CreateMany(devices []Device, c context.Context) ([]Device, error) {
paddy@0 159 storer, err := getStorer(c)
paddy@0 160 if err != nil {
paddy@0 161 log.Printf("Error retrieving Storer: %+v\n", err)
paddy@0 162 return []Device{}, err
paddy@0 163 }
paddy@0 164 modified := make([]Device, 0, len(devices))
paddy@0 165 for _, device := range devices {
paddy@0 166 if device.ID.IsZero() {
paddy@0 167 device.ID = uuid.NewID()
paddy@0 168 }
paddy@0 169 if device.Created.IsZero() {
paddy@0 170 device.Created = time.Now()
paddy@0 171 }
paddy@0 172 if device.LastSeen.IsZero() {
paddy@0 173 device.LastSeen = time.Now()
paddy@0 174 }
paddy@0 175 modified = append(modified, device)
paddy@0 176 }
paddy@0 177 err = storer.CreateDevices(devices, c)
paddy@0 178 if err != nil {
paddy@0 179 return []Device{}, err
paddy@0 180 }
paddy@0 181 return modified, nil
paddy@0 182 }
paddy@0 183
paddy@0 184 // Create stores the passed Device in the datastore, assigning default values if
paddy@0 185 // necessary. The Devices that were ultimately stored (including any default values, if
paddy@0 186 // applicable) are returned.
paddy@0 187 func Create(device Device, c context.Context) (Device, error) {
paddy@0 188 devices, err := CreateMany([]Device{device}, c)
paddy@0 189 if err != nil {
paddy@0 190 return Device{}, err
paddy@0 191 }
paddy@0 192 // There should never be a case where we don't return a result.
paddy@0 193 // Ideally, we'd return an error here instead of letting the panic
paddy@0 194 // happen, but seeing as I can't come up with a reason the error would
paddy@0 195 // occur, I'm having trouble coming up with a reasonable error to return.
paddy@0 196 return devices[0], nil
paddy@0 197 }
paddy@0 198
paddy@0 199 // ListByOwner returns a slice of all the Devices with an Owner property that
paddy@0 200 // matches the passed ID. There's no guarantee on the order the Devices will be
paddy@0 201 // returned in.
paddy@0 202 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
paddy@0 203 // BUG(paddy): Eventually, we'll need to support paging for devices. But right now, I don't foresee any user creating enough of them to make pagination worthwhile.
paddy@0 204 storer, err := getStorer(c)
paddy@0 205 if err != nil {
paddy@0 206 log.Printf("Error retrieving Storer: %+v\n", err)
paddy@0 207 return []Device{}, err
paddy@0 208 }
paddy@0 209 devices, err := storer.ListDevicesByOwner(user, c)
paddy@0 210 return devices, err
paddy@0 211 }
paddy@7 212
paddy@12 213 // ToMap transforms the passed slice into a map by using the String method of
paddy@12 214 // each Device's ID as the key, and the Device as the value.
paddy@7 215 func ToMap(devices []Device) map[string]Device {
paddy@7 216 results := make(map[string]Device, len(devices))
paddy@7 217 for _, device := range devices {
paddy@7 218 results[device.ID.String()] = device
paddy@7 219 }
paddy@7 220 return results
paddy@7 221 }
paddy@7 222
paddy@12 223 // ToSlice transforms the passed map of Devices into a slice of Devices.
paddy@7 224 func ToSlice(devices map[string]Device) []Device {
paddy@7 225 results := make([]Device, 0, len(devices))
paddy@7 226 for _, device := range devices {
paddy@7 227 results = append(results, device)
paddy@7 228 }
paddy@7 229 return results
paddy@7 230 }