go-micro.dev/v5@v5.12.0/store/nats-js-kv/nats_test.go (about)

     1  package natsjskv
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  	"github.com/nats-io/nats.go"
    11  	"github.com/pkg/errors"
    12  	"go-micro.dev/v5/store"
    13  )
    14  
    15  func TestNats(t *testing.T) {
    16  	// Setup without calling Init on purpose
    17  	var err error
    18  	for i := 0; i < 5; i++ {
    19  		ctx, cancel := context.WithCancel(context.Background())
    20  		defer cancel()
    21  		addr := startNatsServer(ctx, t)
    22  		s := NewStore(store.Nodes(addr), EncodeKeys())
    23  
    24  		// Test String method
    25  		t.Log("Testing:", s.String())
    26  
    27  		err = basicTest(t, s)
    28  		if err != nil {
    29  			t.Log(err)
    30  			continue
    31  		}
    32  
    33  		// Test reading non-existing key
    34  		r, err := s.Read("this-is-a-random-key")
    35  		if !errors.Is(err, store.ErrNotFound) {
    36  			t.Errorf("Expected %# v, got %# v", store.ErrNotFound, err)
    37  		}
    38  		if len(r) > 0 {
    39  			t.Fatal("Lenth should be 0")
    40  		}
    41  		err = s.Close()
    42  		if err != nil {
    43  			t.Logf("Failed to close store: %v", err)
    44  		}
    45  		cancel()
    46  		return
    47  	}
    48  	t.Fatal(err)
    49  }
    50  
    51  func TestOptions(t *testing.T) {
    52  	ctx, cancel := context.WithCancel(context.Background())
    53  	s := testSetup(ctx, t,
    54  		DefaultMemory(),
    55  
    56  		// Having a non-default description will trigger nats.ErrStreamNameAlreadyInUse
    57  		//  since the buckets have been created in previous tests with a different description.
    58  		//
    59  		// NOTE: this is only the case with a manually set up server, not with current
    60  		//       test setup, where new servers are started for each test.
    61  		DefaultDescription("My fancy description"),
    62  
    63  		// Option has no effect in this context, just to test setting the option
    64  		JetStreamOptions(nats.PublishAsyncMaxPending(256)),
    65  
    66  		// Sets a custom NATS client name, just to test the NatsOptions() func
    67  		NatsOptions(nats.Options{Name: "Go NATS Store Plugin Tests Client"}),
    68  
    69  		KeyValueOptions(&nats.KeyValueConfig{
    70  			Bucket:      "TestBucketName",
    71  			Description: "This bucket is not used",
    72  			TTL:         5 * time.Minute,
    73  			MaxBytes:    1024,
    74  			Storage:     nats.MemoryStorage,
    75  			Replicas:    1,
    76  		}),
    77  
    78  		// Encode keys to avoid character limitations
    79  		EncodeKeys(),
    80  	)
    81  	defer cancel()
    82  
    83  	if err := basicTest(t, s); err != nil {
    84  		t.Fatal(err)
    85  	}
    86  }
    87  
    88  func TestTTL(t *testing.T) {
    89  	ctx, cancel := context.WithCancel(context.Background())
    90  
    91  	ttl := 500 * time.Millisecond
    92  	s := testSetup(ctx, t,
    93  		DefaultTTL(ttl),
    94  
    95  		// Since these buckets will be new they will have the new description
    96  		DefaultDescription("My fancy description"),
    97  	)
    98  	defer cancel()
    99  
   100  	// Use a uuid to make sure a new bucket is created when using local server
   101  	id := uuid.New().String()
   102  	for _, r := range table {
   103  		if err := s.Write(r.Record, store.WriteTo(r.Database+id, r.Table)); err != nil {
   104  			t.Fatal(err)
   105  		}
   106  	}
   107  
   108  	time.Sleep(ttl * 2)
   109  
   110  	for _, r := range table {
   111  		res, err := s.Read(r.Record.Key, store.ReadFrom(r.Database+id, r.Table))
   112  		if !errors.Is(err, store.ErrNotFound) {
   113  			t.Errorf("Expected %# v, got %# v", store.ErrNotFound, err)
   114  		}
   115  		if len(res) > 0 {
   116  			t.Fatal("Fetched record while it should have expired")
   117  		}
   118  	}
   119  }
   120  
   121  func TestMetaData(t *testing.T) {
   122  	ctx, cancel := context.WithCancel(context.Background())
   123  	s := testSetup(ctx, t)
   124  	defer cancel()
   125  
   126  	record := store.Record{
   127  		Key:   "KeyOne",
   128  		Value: []byte("Some value"),
   129  		Metadata: map[string]interface{}{
   130  			"meta-one": "val",
   131  			"meta-two": 5,
   132  		},
   133  		Expiry: 0,
   134  	}
   135  	bucket := "meta-data-test"
   136  	if err := s.Write(&record, store.WriteTo(bucket, "")); err != nil {
   137  		t.Fatal(err)
   138  	}
   139  
   140  	r, err := s.Read(record.Key, store.ReadFrom(bucket, ""))
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	if len(r) == 0 {
   145  		t.Fatal("No results found")
   146  	}
   147  
   148  	m := r[0].Metadata
   149  	if m["meta-one"].(string) != record.Metadata["meta-one"].(string) ||
   150  		m["meta-two"].(float64) != float64(record.Metadata["meta-two"].(int)) {
   151  		t.Fatalf("Metadata does not match: (%+v) != (%+v)", m, record.Metadata)
   152  	}
   153  }
   154  
   155  func TestDelete(t *testing.T) {
   156  	ctx, cancel := context.WithCancel(context.Background())
   157  	s := testSetup(ctx, t)
   158  	defer cancel()
   159  
   160  	for _, r := range table {
   161  		if err := s.Write(r.Record, store.WriteTo(r.Database, r.Table)); err != nil {
   162  			t.Fatal(err)
   163  		}
   164  
   165  		if err := s.Delete(r.Record.Key, store.DeleteFrom(r.Database, r.Table)); err != nil {
   166  			t.Fatal(err)
   167  		}
   168  		time.Sleep(time.Second)
   169  
   170  		res, err := s.Read(r.Record.Key, store.ReadFrom(r.Database, r.Table))
   171  		if !errors.Is(err, store.ErrNotFound) {
   172  			t.Errorf("Expected %# v, got %# v", store.ErrNotFound, err)
   173  		}
   174  		if len(res) > 0 {
   175  			t.Fatalf("Failed to delete %s:%s from %s %s (len: %d)", r.Record.Key, r.Record.Value, r.Database, r.Table, len(res))
   176  		}
   177  	}
   178  }
   179  
   180  func TestList(t *testing.T) {
   181  	ctx, cancel := context.WithCancel(context.Background())
   182  	s := testSetup(ctx, t)
   183  	defer cancel()
   184  
   185  	for _, r := range table {
   186  		if err := s.Write(r.Record, store.WriteTo(r.Database, r.Table)); err != nil {
   187  			t.Fatal(err)
   188  		}
   189  	}
   190  
   191  	l := []struct {
   192  		Database string
   193  		Table    string
   194  		Length   int
   195  		Prefix   string
   196  		Suffix   string
   197  		Offset   int
   198  		Limit    int
   199  	}{
   200  		{Length: 7},
   201  		{Database: "prefix-test", Length: 7},
   202  		{Database: "prefix-test", Offset: 2, Length: 5},
   203  		{Database: "prefix-test", Offset: 2, Limit: 3, Length: 3},
   204  		{Database: "prefix-test", Table: "names", Length: 3},
   205  		{Database: "prefix-test", Table: "cities", Length: 4},
   206  		{Database: "prefix-test", Table: "cities", Suffix: "City", Length: 3},
   207  		{Database: "prefix-test", Table: "cities", Suffix: "City", Limit: 2, Length: 2},
   208  		{Database: "prefix-test", Table: "cities", Suffix: "City", Offset: 1, Length: 2},
   209  		{Prefix: "test", Length: 1},
   210  		{Table: "some_table", Prefix: "test", Suffix: "test", Length: 2},
   211  	}
   212  
   213  	for i, entry := range l {
   214  		// Test listing keys
   215  		keys, err := s.List(
   216  			store.ListFrom(entry.Database, entry.Table),
   217  			store.ListPrefix(entry.Prefix),
   218  			store.ListSuffix(entry.Suffix),
   219  			store.ListOffset(uint(entry.Offset)),
   220  			store.ListLimit(uint(entry.Limit)),
   221  		)
   222  		if err != nil {
   223  			t.Fatal(err)
   224  		}
   225  		if len(keys) != entry.Length {
   226  			t.Fatalf("Length of returned keys is invalid for test %d - %+v (%d)", i+1, entry, len(keys))
   227  		}
   228  
   229  		// Test reading keys
   230  		if entry.Prefix != "" || entry.Suffix != "" {
   231  			var key string
   232  			options := []store.ReadOption{
   233  				store.ReadFrom(entry.Database, entry.Table),
   234  				store.ReadLimit(uint(entry.Limit)),
   235  				store.ReadOffset(uint(entry.Offset)),
   236  			}
   237  			if entry.Prefix != "" {
   238  				key = entry.Prefix
   239  				options = append(options, store.ReadPrefix())
   240  			}
   241  			if entry.Suffix != "" {
   242  				key = entry.Suffix
   243  				options = append(options, store.ReadSuffix())
   244  			}
   245  			r, err := s.Read(key, options...)
   246  			if err != nil {
   247  				t.Fatal(err)
   248  			}
   249  			if len(r) != entry.Length {
   250  				t.Fatalf("Length of read keys is invalid for test %d - %+v (%d)", i+1, entry, len(r))
   251  			}
   252  		}
   253  	}
   254  }
   255  
   256  func TestDeleteBucket(t *testing.T) {
   257  	ctx, cancel := context.WithCancel(context.Background())
   258  	s := testSetup(ctx, t)
   259  	defer cancel()
   260  
   261  	for _, r := range table {
   262  		if err := s.Write(r.Record, store.WriteTo(r.Database, r.Table)); err != nil {
   263  			t.Fatal(err)
   264  		}
   265  	}
   266  
   267  	bucket := "prefix-test"
   268  	if err := s.Delete(bucket, DeleteBucket()); err != nil {
   269  		t.Fatal(err)
   270  	}
   271  
   272  	keys, err := s.List(store.ListFrom(bucket, ""))
   273  	if err != nil && !errors.Is(err, ErrBucketNotFound) {
   274  		t.Fatalf("Failed to delete bucket: %v", err)
   275  	}
   276  
   277  	if len(keys) > 0 {
   278  		t.Fatal("Length of key list should be 0 after bucket deletion")
   279  	}
   280  
   281  	r, err := s.Read("", store.ReadPrefix(), store.ReadFrom(bucket, ""))
   282  	if err != nil && !errors.Is(err, ErrBucketNotFound) {
   283  		t.Fatalf("Failed to delete bucket: %v", err)
   284  	}
   285  	if len(r) > 0 {
   286  		t.Fatal("Length of record list should be 0 after bucket deletion", len(r))
   287  	}
   288  }
   289  
   290  func TestEnforceLimits(t *testing.T) {
   291  	s := []string{"a", "b", "c", "d"}
   292  	var testCasts = []struct {
   293  		Alias    string
   294  		Offset   uint
   295  		Limit    uint
   296  		Expected []string
   297  	}{
   298  		{"plain", 0, 0, []string{"a", "b", "c", "d"}},
   299  		{"offset&limit-1", 1, 3, []string{"b", "c", "d"}},
   300  		{"offset&limit-2", 1, 1, []string{"b"}},
   301  		{"offset=length", 4, 0, []string{}},
   302  		{"offset>length", 222, 0, []string{}},
   303  		{"limit>length", 0, 36, []string{"a", "b", "c", "d"}},
   304  	}
   305  	for _, tc := range testCasts {
   306  		actual := enforceLimits(s, tc.Limit, tc.Offset)
   307  		if !reflect.DeepEqual(actual, tc.Expected) {
   308  			t.Fatalf("%s: Expected %v, got %v", tc.Alias, tc.Expected, actual)
   309  		}
   310  	}
   311  }
   312  
   313  func basicTest(t *testing.T, s store.Store) error {
   314  	t.Helper()
   315  	for _, test := range table {
   316  		if err := s.Write(test.Record, store.WriteTo(test.Database, test.Table)); err != nil {
   317  			return errors.Wrap(err, "Failed to write record in basic test")
   318  		}
   319  		r, err := s.Read(test.Record.Key, store.ReadFrom(test.Database, test.Table))
   320  		if err != nil {
   321  			return errors.Wrap(err, "Failed to read record in basic test")
   322  		}
   323  		if len(r) == 0 {
   324  			t.Fatalf("No results found for %s (%s) %s", test.Record.Key, test.Database, test.Table)
   325  		}
   326  
   327  		key := test.Record.Key
   328  		val1 := string(test.Record.Value)
   329  
   330  		key2 := r[0].Key
   331  		val2 := string(r[0].Value)
   332  		if val1 != val2 {
   333  			t.Fatalf("Value not equal for (%s: %s) != (%s: %s)", key, val1, key2, val2)
   334  		}
   335  	}
   336  	return nil
   337  }