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 })