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  }