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 }