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