github.com/m-lab/locate@v0.17.6/memorystore/client_test.go (about) 1 package memorystore 2 3 import ( 4 "encoding/json" 5 "errors" 6 "math" 7 "testing" 8 9 "github.com/go-test/deep" 10 "github.com/gomodule/redigo/redis" 11 "github.com/m-lab/go/testingx" 12 v2 "github.com/m-lab/locate/api/v2" 13 "github.com/m-lab/locate/connection/testdata" 14 "github.com/rafaeljusto/redigomock" 15 ) 16 17 func setUpTest[V any]() (*redigomock.Conn, *client[V]) { 18 conn := redigomock.NewConn() 19 pool := redis.Pool{ 20 Dial: func() (redis.Conn, error) { 21 return conn, nil 22 }, 23 } 24 c := NewClient[V](&pool) 25 return conn, c 26 } 27 28 func TestPut_MarshalError(t *testing.T) { 29 conn, client := setUpTest[v2.HeartbeatMessage]() 30 31 hset := conn.GenericCommand("HSET") 32 r := *testdata.FakeRegistration.Registration 33 r.Latitude = math.Inf(1) 34 opts := &PutOptions{FieldMustExist: "", WithExpire: true} 35 err := client.Put(testdata.FakeHostname, "Registration", &r, opts) 36 37 if conn.Stats(hset) > 0 { 38 t.Fatal("Put() failure, HSET command should not be called, want: marshal error") 39 } 40 41 if err == nil { 42 t.Error("Put() error: nil, want: marshal error") 43 } 44 } 45 46 func TestPut_HSETError(t *testing.T) { 47 conn, client := setUpTest[v2.HeartbeatMessage]() 48 49 hset := conn.GenericCommand("HSET").ExpectError(errors.New("HSET error")) 50 opts := &PutOptions{FieldMustExist: "", WithExpire: true} 51 err := client.Put(testdata.FakeHostname, "Registration", testdata.FakeRegistration.Registration, opts) 52 53 if conn.Stats(hset) != 1 { 54 t.Fatal("Put() failure, HSET command should have been called") 55 } 56 57 if err == nil { 58 t.Error("Put() error: nil, want: HSET error") 59 } 60 } 61 62 func TestPut_EVALError(t *testing.T) { 63 conn, client := setUpTest[v2.HeartbeatMessage]() 64 65 hset := conn.GenericCommand("EVAL").ExpectError(errors.New("EVAL error")) 66 opts := &PutOptions{FieldMustExist: "Registration", WithExpire: true} 67 err := client.Put(testdata.FakeHostname, "Health", testdata.FakeHealth.Health, opts) 68 69 if conn.Stats(hset) != 1 { 70 t.Fatal("Put() failure, EVAL command should have been called") 71 } 72 73 if err == nil { 74 t.Error("Put() error: nil, want: EVAL error") 75 } 76 } 77 78 func TestPut_EXPIREError(t *testing.T) { 79 conn, client := setUpTest[v2.HeartbeatMessage]() 80 81 hset := conn.GenericCommand("HSET").Expect(1) 82 expire := conn.GenericCommand("EXPIRE").ExpectError(errors.New("EXPIRE error")) 83 opts := &PutOptions{FieldMustExist: "", WithExpire: true} 84 err := client.Put(testdata.FakeHostname, "Registration", testdata.FakeRegistration.Registration, opts) 85 86 if conn.Stats(hset) != 1 || conn.Stats(expire) != 1 { 87 t.Fatal("Put() failure, HSET and EXPIRE commands should have been called") 88 } 89 90 if err == nil { 91 t.Error("Put() error: nil, want: EXPIRE error") 92 } 93 } 94 95 func TestPut_Success(t *testing.T) { 96 conn, client := setUpTest[v2.HeartbeatMessage]() 97 98 hset := conn.GenericCommand("HSET").Expect(1) 99 opts := &PutOptions{FieldMustExist: "", WithExpire: false} 100 err := client.Put(testdata.FakeHostname, "Registration", testdata.FakeRegistration.Registration, opts) 101 102 if conn.Stats(hset) != 1 { 103 t.Fatal("Put() failure, HSET command should have been called") 104 } 105 106 if err != nil { 107 t.Errorf("Put() error: %+v, want: nil", err) 108 } 109 } 110 111 func TestPut_SuccessWithEXISTS(t *testing.T) { 112 conn, client := setUpTest[v2.HeartbeatMessage]() 113 114 hset := conn.GenericCommand("EVAL").Expect(1) 115 opts := &PutOptions{FieldMustExist: "Registration", WithExpire: false} 116 err := client.Put(testdata.FakeHostname, "Health", testdata.FakeHealth.Health, opts) 117 118 if conn.Stats(hset) != 1 { 119 t.Fatal("Put() failure, EVAL command should have been called") 120 } 121 122 if err != nil { 123 t.Errorf("Put() error: %+v, want: nil", err) 124 } 125 } 126 127 func TestPut_SuccessWithEXPIRE(t *testing.T) { 128 conn, client := setUpTest[v2.HeartbeatMessage]() 129 130 hset := conn.GenericCommand("HSET").Expect(1) 131 expire := conn.GenericCommand("EXPIRE").Expect(1) 132 opts := &PutOptions{FieldMustExist: "", WithExpire: true} 133 err := client.Put(testdata.FakeHostname, "Registration", testdata.FakeRegistration.Registration, opts) 134 135 if conn.Stats(hset) != 1 || conn.Stats(expire) != 1 { 136 t.Fatal("Put() failure, HSET and EXPIRE commands should have been called") 137 } 138 139 if err != nil { 140 t.Errorf("Put() error: %+v, want: nil", err) 141 } 142 } 143 144 func TestGetAll_SCANError(t *testing.T) { 145 conn, client := setUpTest[v2.HeartbeatMessage]() 146 scan := conn.GenericCommand("SCAN").ExpectError(errors.New("SCAN error")) 147 148 _, err := client.GetAll() 149 150 if conn.Stats(scan) != 1 { 151 t.Fatal("GetAll() failure, SCAN should have been called") 152 } 153 154 if err == nil { 155 t.Error("GetAll() error: nil, want: SCAN error") 156 } 157 } 158 159 func TestGetAll_ScanLibraryError(t *testing.T) { 160 conn, client := setUpTest[v2.HeartbeatMessage]() 161 162 // Only returning one argument will cause the `redis.Scan()` 163 // call to fail with a "redigo.Scan: array short" error. 164 scan := conn.Command("SCAN", 0).Expect([]interface{}{ 165 int64(10), 166 }) 167 168 _, err := client.GetAll() 169 170 if conn.Stats(scan) != 1 { 171 t.Fatal("GetAll() failure, SCAN should have been called") 172 } 173 174 if err == nil { 175 t.Error("GetAll() error: nil, want: redigo.Scan error") 176 } 177 } 178 179 func TestGetAll_GetError(t *testing.T) { 180 conn, client := setUpTest[v2.HeartbeatMessage]() 181 182 scan := conn.Command("SCAN", 0).Expect([]interface{}{ 183 int64(10), []interface{}{testdata.FakeHostname}, 184 }) 185 186 // This will return an error in the inner get() call. 187 hgetall := conn.GenericCommand("HGETALL").ExpectError(errors.New("HGETALL error")) 188 189 _, err := client.GetAll() 190 191 if conn.Stats(scan) != 1 || conn.Stats(hgetall) != 1 { 192 t.Fatal("GetAll() failure, SCAN and HGETALL should have been called") 193 } 194 195 if err == nil { 196 t.Error("GetAll() error: nil, want: get error") 197 } 198 } 199 200 func TestGetAll_Success(t *testing.T) { 201 conn, client := setUpTest[v2.HeartbeatMessage]() 202 203 scan := conn.Command("SCAN", 0).Expect([]interface{}{ 204 int64(10), []interface{}{testdata.FakeHostname}, 205 }) 206 scan2 := conn.Command("SCAN", 10).Expect([]interface{}{ 207 int64(0), nil, 208 }) 209 210 hbm := v2.HeartbeatMessage{} 211 hbm.Registration = testdata.FakeRegistration.Registration 212 hbm.Health = testdata.FakeHealth.Health 213 rBytes, err := json.Marshal(hbm.Registration) 214 testingx.Must(t, err, "failed to marshal registration") 215 hBytes, err := json.Marshal(hbm.Health) 216 testingx.Must(t, err, "failed to marshal health") 217 hgetall := conn.Command("HGETALL", testdata.FakeHostname).Expect([]interface{}{ 218 []byte("Registration"), rBytes, []byte("Health"), hBytes, 219 }) 220 221 got, err := client.GetAll() 222 223 if conn.Stats(scan) != 1 || conn.Stats(scan2) != 1 || conn.Stats(hgetall) != 1 { 224 t.Fatal("GetAll() failure, SCAN and HGETALL should have been called") 225 } 226 227 if err != nil { 228 t.Fatalf("GetAll() error: %+v, want: nil", err) 229 } 230 231 want := map[string]v2.HeartbeatMessage{testdata.FakeHostname: hbm} 232 if diff := deep.Equal(got, want); diff != nil { 233 t.Errorf("GetAll() incorrect output; got: %+v, want: %+v", got, want) 234 } 235 } 236 237 func TestGet_HGETALLError(t *testing.T) { 238 conn, client := setUpTest[v2.HeartbeatMessage]() 239 240 hgetall := conn.GenericCommand("HGETALL").ExpectError(errors.New("HGETALL error")) 241 _, err := client.get("", conn) 242 243 if conn.Stats(hgetall) != 1 { 244 t.Fatal("get() failure, HGETALL should have been called") 245 } 246 247 if err == nil { 248 t.Error("get() error: nil, want: HGETALL error") 249 } 250 } 251 252 func TestGet_ScanStructError(t *testing.T) { 253 // ScanStruct fails when it does not know how to scan a field 254 // that doesn't implement the Scanner interface. 255 conn, client := setUpTest[v2.MonitoringResult]() 256 257 hgetall := conn.GenericCommand("HGETALL").Expect([]interface{}{ 258 []byte("Error"), &v2.Error{}, 259 }) 260 261 _, err := client.get("foo", conn) 262 263 if conn.Stats(hgetall) != 1 { 264 t.Fatal("get() failure, HGETALL should have been called") 265 } 266 267 if err == nil { 268 t.Error("get() error: nil, want: ScanStruct error") 269 } 270 } 271 272 func TestDel_Success(t *testing.T) { 273 conn, client := setUpTest[v2.HeartbeatMessage]() 274 275 delCmd := conn.Command("DEL", testdata.FakeHostname).Expect(1) 276 err := client.Del(testdata.FakeHostname) 277 278 if conn.Stats(delCmd) != 1 { 279 t.Fatal("Del() failure, DEL should have been called") 280 } 281 282 if err != nil { 283 t.Errorf("Del() error: %+v, want: nil", err) 284 } 285 } 286 287 func TestDel_Error(t *testing.T) { 288 conn, client := setUpTest[v2.HeartbeatMessage]() 289 290 delCmd := conn.Command("DEL", testdata.FakeHostname).ExpectError(errors.New("DEL error")) 291 err := client.Del(testdata.FakeHostname) 292 293 if conn.Stats(delCmd) != 1 { 294 t.Fatal("Del() failure, DEL should have been called") 295 } 296 297 if err == nil { 298 t.Error("Del() error: nil, want: DEL error", err) 299 } 300 }