github.com/epsagon/epsagon-go@v1.39.0/wrappers/redis/redis_test.go (about)

     1  package epsagonredis_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/alicebob/miniredis/v2"
    10  	"github.com/epsagon/epsagon-go/epsagon"
    11  	"github.com/epsagon/epsagon-go/protocol"
    12  	"github.com/epsagon/epsagon-go/tracer"
    13  	epsagonredis "github.com/epsagon/epsagon-go/wrappers/redis"
    14  	"github.com/go-redis/redis/v8"
    15  	. "github.com/onsi/ginkgo"
    16  	. "github.com/onsi/gomega"
    17  	"github.com/onsi/gomega/gstruct"
    18  )
    19  
    20  func TestRedisWrapper(t *testing.T) {
    21  	RegisterFailHandler(Fail)
    22  	RunSpecs(t, "Redis Wrapper")
    23  }
    24  
    25  var _ = Describe("Redis wrapper", func() {
    26  	var (
    27  		events      []*protocol.Event
    28  		client      *redis.Client
    29  		ctx         context.Context
    30  		tracerMock  *tracer.MockedEpsagonTracer
    31  		redisServer *miniredis.Miniredis
    32  	)
    33  
    34  	BeforeEach(func() {
    35  		events = make([]*protocol.Event, 0)
    36  		tracerMock = &tracer.MockedEpsagonTracer{
    37  			Events: &events,
    38  			Config: &tracer.Config{
    39  				Disable:      true,
    40  				TestMode:     true,
    41  				MetadataOnly: true,
    42  			},
    43  		}
    44  		ctx = epsagon.ContextWithTracer(tracerMock)
    45  
    46  		server, err := miniredis.Run()
    47  		if err != nil {
    48  			panic(err)
    49  		} else {
    50  			redisServer = server
    51  		}
    52  
    53  		client = epsagonredis.NewClient(&redis.Options{
    54  			Addr:     redisServer.Addr(),
    55  			Password: "",
    56  			DB:       0,
    57  		}, ctx)
    58  	})
    59  
    60  	AfterEach(func() {
    61  		redisServer.Close()
    62  	})
    63  
    64  	Context("Single operation", func() {
    65  		It("Adds event, MetadataOnly=true", func() {
    66  			tracerMock.Config.MetadataOnly = true
    67  			const (
    68  				key   = "the_key"
    69  				value = "the_value"
    70  			)
    71  			err := redisServer.Set(key, value)
    72  			if err != nil {
    73  				Fail("Test setup failed")
    74  			}
    75  
    76  			result := client.Get(ctx, key)
    77  
    78  			Expect(result.Val()).To(Equal(value))
    79  			Expect(len(events)).To(Equal(1))
    80  			Expect(*events[0]).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
    81  				"Id":        ContainSubstring("redis-"),
    82  				"StartTime": BeNumerically(">", 0),
    83  				"Duration":  BeNumerically(">", 0),
    84  				"ErrorCode": Equal(protocol.ErrorCode_OK),
    85  				"Exception": BeNil(),
    86  			}))
    87  			Expect(*events[0].Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
    88  				"Name":      Equal(redisServer.Host()),
    89  				"Type":      Equal("redis"),
    90  				"Operation": Equal("get"),
    91  				"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
    92  					"Redis Host":     Equal(redisServer.Host()),
    93  					"Redis Port":     Equal(redisServer.Port()),
    94  					"Redis DB Index": Equal("0"),
    95  				}),
    96  			}))
    97  			Expect(events[0].Resource.Metadata["Command Arguments"]).To(BeEmpty())
    98  			Expect(events[0].Resource.Metadata["redis.response"]).To(BeEmpty())
    99  		})
   100  
   101  		It("Adds event, MetadataOnly=false", func() {
   102  			tracerMock.Config.MetadataOnly = false
   103  			const (
   104  				key   = "the_key"
   105  				value = "the_value"
   106  			)
   107  			err := redisServer.Set(key, value)
   108  			if err != nil {
   109  				Fail("Test setup failed")
   110  			}
   111  
   112  			result := client.Get(ctx, key)
   113  
   114  			Expect(result.Val()).To(Equal(value))
   115  			Expect(len(events)).To(Equal(1))
   116  			Expect(*events[0]).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   117  				"Id":        ContainSubstring("redis-"),
   118  				"StartTime": BeNumerically(">", 0),
   119  				"Duration":  BeNumerically(">", 0),
   120  				"ErrorCode": Equal(protocol.ErrorCode_OK),
   121  				"Exception": BeNil(),
   122  			}))
   123  
   124  			cmdArgs, _ := json.Marshal([]string{"get", key})
   125  			Expect(*events[0].Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   126  				"Name":      Equal(redisServer.Host()),
   127  				"Type":      Equal("redis"),
   128  				"Operation": Equal("get"),
   129  				"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
   130  					"Redis Host":        Equal(redisServer.Host()),
   131  					"Redis Port":        Equal(redisServer.Port()),
   132  					"Redis DB Index":    Equal("0"),
   133  					"Command Arguments": Equal(string(cmdArgs)),
   134  					"redis.response":    Equal(fmt.Sprintf("get %s: %s", key, value)),
   135  				}),
   136  			}))
   137  		})
   138  
   139  		It("Adds multiple events", func() {
   140  			const (
   141  				key   = "the_key"
   142  				value = "the_value"
   143  			)
   144  			err := redisServer.Set(key, value)
   145  			if err != nil {
   146  				Fail("Test setup failed")
   147  			}
   148  
   149  			getResult1 := client.Get(ctx, key)
   150  			getResult2 := client.Get(ctx, key)
   151  
   152  			Expect(getResult1.Val()).To(Equal(value))
   153  			Expect(getResult2.Val()).To(Equal(value))
   154  			Expect(len(events)).To(Equal(2))
   155  
   156  			for _, event := range events {
   157  				Expect(*event).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   158  					"Id":        ContainSubstring("redis-"),
   159  					"StartTime": BeNumerically(">", 0),
   160  					"Duration":  BeNumerically(">", 0),
   161  					"ErrorCode": Equal(protocol.ErrorCode_OK),
   162  					"Exception": BeNil(),
   163  				}))
   164  				Expect(*event.Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   165  					"Name":      Equal(redisServer.Host()),
   166  					"Type":      Equal("redis"),
   167  					"Operation": Equal("get"),
   168  					"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
   169  						"Redis Host":     Equal(redisServer.Host()),
   170  						"Redis Port":     Equal(redisServer.Port()),
   171  						"Redis DB Index": Equal("0"),
   172  					}),
   173  				}))
   174  				Expect(event.Resource.Metadata["Command Arguments"]).To(BeEmpty())
   175  				Expect(event.Resource.Metadata["redis.response"]).To(BeEmpty())
   176  			}
   177  		})
   178  
   179  		It("Adds error event", func() {
   180  			const (
   181  				key           = "the_key"
   182  				value         = "the_value"
   183  				expectedError = "ERR value is not an integer or out of range"
   184  			)
   185  			err := redisServer.Set(key, value)
   186  			if err != nil {
   187  				Fail("Test setup failed")
   188  			}
   189  
   190  			// trying to increment string value by one
   191  			result := client.Incr(ctx, key)
   192  
   193  			Expect(result.Err().Error()).To(Equal(expectedError))
   194  			Expect(len(events)).To(Equal(1))
   195  			Expect(*events[0]).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   196  				"Id":        ContainSubstring("redis-"),
   197  				"StartTime": BeNumerically(">", 0),
   198  				"Duration":  BeNumerically(">", 0),
   199  				"ErrorCode": Equal(protocol.ErrorCode_EXCEPTION),
   200  			}))
   201  			Expect(*events[0].Exception).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   202  				"Message":   Equal(expectedError),
   203  				"Time":      BeNumerically(">", 0),
   204  				"Traceback": Not(BeEmpty()),
   205  			}))
   206  			Expect(*events[0].Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   207  				"Name":      Equal(redisServer.Host()),
   208  				"Type":      Equal("redis"),
   209  				"Operation": Equal("incr"),
   210  				"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
   211  					"Redis Host":     Equal(redisServer.Host()),
   212  					"Redis Port":     Equal(redisServer.Port()),
   213  					"Redis DB Index": Equal("0"),
   214  				}),
   215  			}))
   216  			Expect(events[0].Resource.Metadata["Command Arguments"]).To(BeEmpty())
   217  			Expect(events[0].Resource.Metadata["redis.response"]).To(BeEmpty())
   218  		})
   219  
   220  		It("Recovers from add event panic", func() {
   221  			tracerMock.PanicAddEvent = true
   222  			const (
   223  				key   = "the_key"
   224  				value = "the_value"
   225  			)
   226  			err := redisServer.Set(key, value)
   227  			if err != nil {
   228  				Fail("Test setup failed")
   229  			}
   230  
   231  			result := client.Get(ctx, key)
   232  
   233  			Expect(result.Val()).To(Equal(value))
   234  			Expect(len(events)).To(Equal(0))
   235  		})
   236  
   237  		Context("Pipeline operations", func() {
   238  			It("Adds pipeline event, MetadataOnly=true", func() {
   239  				tracerMock.Config.MetadataOnly = true
   240  				const (
   241  					key                 = "the_key"
   242  					value               = "the_value"
   243  					expectedSetResponse = "set the_key the_value: OK"
   244  					expectedGetResponse = "get the_key: the_value"
   245  				)
   246  
   247  				pipe := client.Pipeline()
   248  				pipe.Set(ctx, key, value, 0)
   249  				pipe.Get(ctx, key)
   250  				result, err := pipe.Exec(ctx)
   251  				if err != nil {
   252  					Fail("Failed pipeline test setup")
   253  				}
   254  
   255  				Expect(result[0].String()).To(Equal(expectedSetResponse))
   256  				Expect(result[1].String()).To(Equal(expectedGetResponse))
   257  				Expect(len(events)).To(Equal(1))
   258  				Expect(*events[0]).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   259  					"Id":        ContainSubstring("redis-"),
   260  					"StartTime": BeNumerically(">", 0),
   261  					"Duration":  BeNumerically(">", 0),
   262  					"ErrorCode": Equal(protocol.ErrorCode_OK),
   263  					"Exception": BeNil(),
   264  				}))
   265  				Expect(*events[0].Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   266  					"Name":      Equal(redisServer.Host()),
   267  					"Type":      Equal("redis"),
   268  					"Operation": Equal("Pipeline"),
   269  					"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
   270  						"Redis Host":     Equal(redisServer.Host()),
   271  						"Redis Port":     Equal(redisServer.Port()),
   272  						"Redis DB Index": Equal("0"),
   273  					}),
   274  				}))
   275  				Expect(events[0].Resource.Metadata["Command Arguments"]).To(BeEmpty())
   276  				Expect(events[0].Resource.Metadata["redis.response"]).To(BeEmpty())
   277  			})
   278  
   279  			It("Adds pipeline event, MetadataOnly=false", func() {
   280  				tracerMock.Config.MetadataOnly = false
   281  				const (
   282  					key                 = "the_key"
   283  					value               = "the_value"
   284  					expectedSetResponse = "set the_key the_value: OK"
   285  					expectedGetResponse = "get the_key: the_value"
   286  				)
   287  
   288  				pipe := client.Pipeline()
   289  				pipe.Set(ctx, key, value, 0)
   290  				pipe.Get(ctx, key)
   291  				result, err := pipe.Exec(ctx)
   292  				if err != nil {
   293  					Fail("Failed pipeline test setup")
   294  				}
   295  
   296  				Expect(result[0].String()).To(Equal(expectedSetResponse))
   297  				Expect(result[1].String()).To(Equal(expectedGetResponse))
   298  				Expect(len(events)).To(Equal(1))
   299  				Expect(*events[0]).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   300  					"Id":        ContainSubstring("redis-"),
   301  					"StartTime": BeNumerically(">", 0),
   302  					"Duration":  BeNumerically(">", 0),
   303  					"ErrorCode": Equal(protocol.ErrorCode_OK),
   304  					"Exception": BeNil(),
   305  				}))
   306  
   307  				cmdArgs, _ := json.Marshal([][]string{
   308  					{"set", key, value},
   309  					{"get", key},
   310  				})
   311  				redisResponse, _ := json.Marshal([]string{
   312  					fmt.Sprintf("set %s %s: OK", key, value),
   313  					fmt.Sprintf("get %s: %s", key, value),
   314  				})
   315  				Expect(*events[0].Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   316  					"Name":      Equal(redisServer.Host()),
   317  					"Type":      Equal("redis"),
   318  					"Operation": Equal("Pipeline"),
   319  					"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
   320  						"Redis Host":        Equal(redisServer.Host()),
   321  						"Redis Port":        Equal(redisServer.Port()),
   322  						"Redis DB Index":    Equal("0"),
   323  						"Command Arguments": Equal(string(cmdArgs)),
   324  						"redis.response":    Equal(string(redisResponse)),
   325  					}),
   326  				}))
   327  			})
   328  
   329  			It("Adds pipeline error event", func() {
   330  				const (
   331  					key           = "the_key"
   332  					value         = "the_value"
   333  					expectedError = "ERR value is not an integer or out of range"
   334  				)
   335  
   336  				pipe := client.Pipeline()
   337  				pipe.Set(ctx, key, value, 0)
   338  				// trying to increment string value by one
   339  				pipe.Incr(ctx, key)
   340  				pipe.Exec(ctx)
   341  
   342  				Expect(len(events)).To(Equal(1))
   343  				Expect(*events[0]).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   344  					"Id":        ContainSubstring("redis-"),
   345  					"StartTime": BeNumerically(">", 0),
   346  					"Duration":  BeNumerically(">", 0),
   347  					"ErrorCode": Equal(protocol.ErrorCode_EXCEPTION),
   348  				}))
   349  
   350  				errorMessage, _ := json.Marshal([]string{expectedError})
   351  				Expect(*events[0].Exception).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   352  					"Message":   Equal(string(errorMessage)),
   353  					"Time":      BeNumerically(">", 0),
   354  					"Traceback": Not(BeEmpty()),
   355  				}))
   356  				Expect(*events[0].Resource).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   357  					"Name":      Equal(redisServer.Host()),
   358  					"Type":      Equal("redis"),
   359  					"Operation": Equal("Pipeline"),
   360  					"Metadata": gstruct.MatchAllKeys(gstruct.Keys{
   361  						"Redis Host":     Equal(redisServer.Host()),
   362  						"Redis Port":     Equal(redisServer.Port()),
   363  						"Redis DB Index": Equal("0"),
   364  					}),
   365  				}))
   366  				Expect(events[0].Resource.Metadata["Command Arguments"]).To(BeEmpty())
   367  				Expect(events[0].Resource.Metadata["redis.response"]).To(BeEmpty())
   368  			})
   369  		})
   370  	})
   371  })