github.com/epsagon/epsagon-go@v1.39.0/wrappers/gin/gin_test.go (about) 1 package epsagongin 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io/ioutil" 7 "net/http/httptest" 8 "testing" 9 "time" 10 11 "github.com/epsagon/epsagon-go/epsagon" 12 "github.com/epsagon/epsagon-go/protocol" 13 "github.com/epsagon/epsagon-go/tracer" 14 "github.com/gin-gonic/gin" 15 . "github.com/onsi/ginkgo" 16 . "github.com/onsi/gomega" 17 ) 18 19 func TestGinWrapper(t *testing.T) { 20 RegisterFailHandler(Fail) 21 RunSpecs(t, "Gin Wrapper") 22 } 23 24 type MockEngine struct { 25 *gin.Engine 26 TestHandler func(handler gin.HandlerFunc) 27 } 28 29 func (me *MockEngine) Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { 30 me.TestHandler(handlers[0]) 31 return nil 32 } 33 34 var _ = Describe("gin_wrapper", func() { 35 Describe("GinRouterWrapper", func() { 36 var ( 37 events []*protocol.Event 38 exceptions []*protocol.Exception 39 wrapper *GinRouterWrapper 40 mockedEngine *MockEngine 41 called bool 42 testGinContext *gin.Context 43 ) 44 BeforeEach(func() { 45 called = false 46 config := &epsagon.Config{Config: tracer.Config{ 47 Disable: true, 48 TestMode: true, 49 }} 50 events = make([]*protocol.Event, 0, 5) 51 exceptions = make([]*protocol.Exception, 0) 52 tracer.GlobalTracer = &tracer.MockedEpsagonTracer{ 53 Events: &events, 54 Exceptions: &exceptions, 55 Labels: make(map[string]interface{}), 56 Config: &config.Config, 57 } 58 mockedEngine = &MockEngine{ 59 Engine: gin.New(), 60 TestHandler: func(handler gin.HandlerFunc) { 61 handler(testGinContext) 62 }, 63 } 64 wrapper = &GinRouterWrapper{ 65 IRouter: mockedEngine, 66 Hostname: "test", 67 Config: config, 68 } 69 body := []byte("hello") 70 testGinContext, _ = gin.CreateTestContext(httptest.NewRecorder()) 71 testGinContext.Request = httptest.NewRequest("POST", "https://www.help.com", ioutil.NopCloser(bytes.NewReader(body))) 72 Expect(testGinContext.Request).NotTo(Equal(nil)) 73 }) 74 Context("Happy Flows", func() { 75 It("calls the wrapped function", func() { 76 mockedEngine.TestHandler = func(handler gin.HandlerFunc) { 77 handler(testGinContext) 78 Expect(called).To(Equal(true)) 79 } 80 wrapper.GET("/test", func(c *gin.Context) { called = true }) 81 }) 82 It("passes the tracer through gin context", func() { 83 mockedEngine.TestHandler = func(handler gin.HandlerFunc) { 84 handler(testGinContext) 85 Expect(called).To(Equal(true)) 86 } 87 wrapper.GET("/test", func(c *gin.Context) { 88 tracer := c.Keys[TracerKey].(tracer.Tracer) 89 tracer.AddLabel("test", "ok") 90 called = true 91 }) 92 Expect( 93 tracer.GlobalTracer.(*tracer.MockedEpsagonTracer).Labels["test"], 94 ).To(Equal("ok")) 95 }) 96 It("Creates a runner and trigger events for handler invocation", func() { 97 config := &epsagon.Config{Config: tracer.Config{ 98 Disable: true, 99 TestMode: true, 100 }} 101 eventsRecievedChan := make(chan bool) 102 tracer.GlobalTracer = &tracer.MockedEpsagonTracer{ 103 Events: &events, 104 Exceptions: &exceptions, 105 Labels: make(map[string]interface{}), 106 Config: &config.Config, 107 DelayAddEvent: true, 108 DelayedEventsChan: eventsRecievedChan, 109 } 110 mockedEngine.TestHandler = func(handler gin.HandlerFunc) { 111 handler(testGinContext) 112 } 113 wrapper.GET("/test", func(c *gin.Context) { 114 called = true 115 }) 116 timer := time.NewTimer(time.Second * 2) 117 for eventsRecieved := 0; eventsRecieved < 2; { 118 select { 119 case <-eventsRecievedChan: 120 eventsRecieved++ 121 case <-timer.C: 122 // timeout - events should have been recieved 123 Expect(false).To(Equal(true)) 124 } 125 } 126 Expect(len(events)).To(Equal(2)) 127 var runnerEvent *protocol.Event 128 for _, event := range events { 129 if event.Origin == "runner" { 130 runnerEvent = event 131 } 132 } 133 Expect(runnerEvent).NotTo(Equal(nil)) 134 Expect(runnerEvent.Resource.Type).To(Equal("gin")) 135 Expect(runnerEvent.Resource.Name).To(Equal("/test")) 136 }) 137 It("Adds correct trigger event", func() { 138 body := []byte("hello world") 139 testGinContext.Request = httptest.NewRequest( 140 "POST", 141 "https://www.help.com/test?hello=world&good=bye", 142 ioutil.NopCloser(bytes.NewReader(body))) 143 wrapper.Hostname = "" 144 wrapper.GET("/test", func(c *gin.Context) { 145 internalHandlerBody, err := ioutil.ReadAll(c.Request.Body) 146 if err != nil { 147 Expect(true).To(Equal(false)) 148 } 149 Expect(internalHandlerBody).To(Equal(body)) 150 called = true 151 c.JSON(200, gin.H{"hello": "world"}) 152 }) 153 Expect(len(events)).To(Equal(2)) 154 var triggerEvent *protocol.Event 155 for _, event := range events { 156 if event.Origin == "trigger" { 157 triggerEvent = event 158 } 159 } 160 Expect(triggerEvent).NotTo(Equal(nil)) 161 Expect(triggerEvent.Resource.Name).To(Equal("www.help.com")) 162 Expect(triggerEvent.Resource.Type).To(Equal("http")) 163 Expect(triggerEvent.Resource.Operation).To(Equal("POST")) 164 expectedQuery, _ := json.Marshal(map[string][]string{ 165 "hello": {"world"}, "good": {"bye"}}) 166 Expect(triggerEvent.Resource.Metadata["query_string_parameters"]).To( 167 Equal(string(expectedQuery))) 168 Expect(triggerEvent.Resource.Metadata["path"]).To( 169 Equal("/test")) 170 Expect(triggerEvent.Resource.Metadata["request_body"]).To( 171 Equal(string(body))) 172 Expect(triggerEvent.Resource.Metadata["response_body"]).To( 173 Equal("{\"hello\":\"world\"}")) 174 Expect(triggerEvent.Resource.Metadata["response_headers"]).To( 175 Equal("{\"Content-Type\":\"application/json; charset=utf-8\"}")) 176 Expect(triggerEvent.Resource.Metadata["status_code"]).To( 177 Equal("200")) 178 }) 179 It("Doesn't collect body and headers if MetadataOnly", func() { 180 config := &epsagon.Config{Config: tracer.Config{ 181 Disable: true, 182 TestMode: true, 183 MetadataOnly: true, 184 }} 185 tracer.GlobalTracer = &tracer.MockedEpsagonTracer{ 186 Events: &events, 187 Exceptions: &exceptions, 188 Labels: make(map[string]interface{}), 189 Config: &config.Config, 190 } 191 wrapper.Config = config 192 body := []byte("hello world") 193 testGinContext.Request = httptest.NewRequest( 194 "GET", 195 "https://www.help.com/test?hello=world&good=bye", 196 ioutil.NopCloser(bytes.NewReader(body))) 197 wrapper.Hostname = "" 198 wrapper.GET("/test", func(c *gin.Context) { 199 internalHandlerBody, err := ioutil.ReadAll(c.Request.Body) 200 if err != nil { 201 Expect(true).To(Equal(false)) 202 } 203 Expect(internalHandlerBody).To(Equal(body)) 204 called = true 205 c.JSON(200, gin.H{"hello": "world"}) 206 }) 207 Expect(len(events)).To(Equal(2)) 208 var triggerEvent *protocol.Event 209 for _, event := range events { 210 if event.Origin == "trigger" { 211 triggerEvent = event 212 } 213 } 214 Expect(triggerEvent).NotTo(Equal(nil)) 215 Expect(triggerEvent.Resource.Metadata["query_string_parameters"]).To( 216 Equal("")) 217 Expect(triggerEvent.Resource.Metadata["request_body"]).To( 218 Equal("")) 219 Expect(triggerEvent.Resource.Metadata["response_body"]).To( 220 Equal("")) 221 Expect(triggerEvent.Resource.Metadata["response_headers"]).To( 222 Equal("")) 223 }) 224 }) 225 Context("Error Flows", func() { 226 It("Adds Exception if handler explodes", func() { 227 errorMessage := "boom" 228 Expect(func() { 229 wrapper.GET("/test", func(c *gin.Context) { 230 panic(errorMessage) 231 }) 232 }).To( 233 PanicWith(epsagon.MatchUserError(errorMessage))) 234 Expect(len(events)).To(Equal(2)) 235 var runnerEvent, triggerEvent *protocol.Event 236 for _, event := range events { 237 if event.Origin == "runner" { 238 runnerEvent = event 239 } 240 if event.Origin == "trigger" { 241 triggerEvent = event 242 } 243 } 244 Expect( 245 tracer.GlobalTracer.(*tracer.MockedEpsagonTracer).Stopped(), 246 ).To(Equal(true)) 247 Expect(runnerEvent.Exception).NotTo(Equal(nil)) 248 Expect(triggerEvent.Exception).NotTo(Equal(nil)) 249 Expect(triggerEvent.Resource.Metadata["status_code"]).To( 250 Equal("500")) 251 }) 252 }) 253 }) 254 })