1// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
2// Use of this source code is governed by a MIT style
3// license that can be found in the LICENSE file.
4
5package gin
6
7import (
8	"bytes"
9	"errors"
10	"fmt"
11	"net/http"
12	"testing"
13	"time"
14
15	"github.com/stretchr/testify/assert"
16)
17
18func init() {
19	SetMode(TestMode)
20}
21
22func TestLogger(t *testing.T) {
23	buffer := new(bytes.Buffer)
24	router := New()
25	router.Use(LoggerWithWriter(buffer))
26	router.GET("/example", func(c *Context) {})
27	router.POST("/example", func(c *Context) {})
28	router.PUT("/example", func(c *Context) {})
29	router.DELETE("/example", func(c *Context) {})
30	router.PATCH("/example", func(c *Context) {})
31	router.HEAD("/example", func(c *Context) {})
32	router.OPTIONS("/example", func(c *Context) {})
33
34	performRequest(router, "GET", "/example?a=100")
35	assert.Contains(t, buffer.String(), "200")
36	assert.Contains(t, buffer.String(), "GET")
37	assert.Contains(t, buffer.String(), "/example")
38	assert.Contains(t, buffer.String(), "a=100")
39
40	// I wrote these first (extending the above) but then realized they are more
41	// like integration tests because they test the whole logging process rather
42	// than individual functions.  Im not sure where these should go.
43	buffer.Reset()
44	performRequest(router, "POST", "/example")
45	assert.Contains(t, buffer.String(), "200")
46	assert.Contains(t, buffer.String(), "POST")
47	assert.Contains(t, buffer.String(), "/example")
48
49	buffer.Reset()
50	performRequest(router, "PUT", "/example")
51	assert.Contains(t, buffer.String(), "200")
52	assert.Contains(t, buffer.String(), "PUT")
53	assert.Contains(t, buffer.String(), "/example")
54
55	buffer.Reset()
56	performRequest(router, "DELETE", "/example")
57	assert.Contains(t, buffer.String(), "200")
58	assert.Contains(t, buffer.String(), "DELETE")
59	assert.Contains(t, buffer.String(), "/example")
60
61	buffer.Reset()
62	performRequest(router, "PATCH", "/example")
63	assert.Contains(t, buffer.String(), "200")
64	assert.Contains(t, buffer.String(), "PATCH")
65	assert.Contains(t, buffer.String(), "/example")
66
67	buffer.Reset()
68	performRequest(router, "HEAD", "/example")
69	assert.Contains(t, buffer.String(), "200")
70	assert.Contains(t, buffer.String(), "HEAD")
71	assert.Contains(t, buffer.String(), "/example")
72
73	buffer.Reset()
74	performRequest(router, "OPTIONS", "/example")
75	assert.Contains(t, buffer.String(), "200")
76	assert.Contains(t, buffer.String(), "OPTIONS")
77	assert.Contains(t, buffer.String(), "/example")
78
79	buffer.Reset()
80	performRequest(router, "GET", "/notfound")
81	assert.Contains(t, buffer.String(), "404")
82	assert.Contains(t, buffer.String(), "GET")
83	assert.Contains(t, buffer.String(), "/notfound")
84}
85
86func TestLoggerWithConfig(t *testing.T) {
87	buffer := new(bytes.Buffer)
88	router := New()
89	router.Use(LoggerWithConfig(LoggerConfig{Output: buffer}))
90	router.GET("/example", func(c *Context) {})
91	router.POST("/example", func(c *Context) {})
92	router.PUT("/example", func(c *Context) {})
93	router.DELETE("/example", func(c *Context) {})
94	router.PATCH("/example", func(c *Context) {})
95	router.HEAD("/example", func(c *Context) {})
96	router.OPTIONS("/example", func(c *Context) {})
97
98	performRequest(router, "GET", "/example?a=100")
99	assert.Contains(t, buffer.String(), "200")
100	assert.Contains(t, buffer.String(), "GET")
101	assert.Contains(t, buffer.String(), "/example")
102	assert.Contains(t, buffer.String(), "a=100")
103
104	// I wrote these first (extending the above) but then realized they are more
105	// like integration tests because they test the whole logging process rather
106	// than individual functions.  Im not sure where these should go.
107	buffer.Reset()
108	performRequest(router, "POST", "/example")
109	assert.Contains(t, buffer.String(), "200")
110	assert.Contains(t, buffer.String(), "POST")
111	assert.Contains(t, buffer.String(), "/example")
112
113	buffer.Reset()
114	performRequest(router, "PUT", "/example")
115	assert.Contains(t, buffer.String(), "200")
116	assert.Contains(t, buffer.String(), "PUT")
117	assert.Contains(t, buffer.String(), "/example")
118
119	buffer.Reset()
120	performRequest(router, "DELETE", "/example")
121	assert.Contains(t, buffer.String(), "200")
122	assert.Contains(t, buffer.String(), "DELETE")
123	assert.Contains(t, buffer.String(), "/example")
124
125	buffer.Reset()
126	performRequest(router, "PATCH", "/example")
127	assert.Contains(t, buffer.String(), "200")
128	assert.Contains(t, buffer.String(), "PATCH")
129	assert.Contains(t, buffer.String(), "/example")
130
131	buffer.Reset()
132	performRequest(router, "HEAD", "/example")
133	assert.Contains(t, buffer.String(), "200")
134	assert.Contains(t, buffer.String(), "HEAD")
135	assert.Contains(t, buffer.String(), "/example")
136
137	buffer.Reset()
138	performRequest(router, "OPTIONS", "/example")
139	assert.Contains(t, buffer.String(), "200")
140	assert.Contains(t, buffer.String(), "OPTIONS")
141	assert.Contains(t, buffer.String(), "/example")
142
143	buffer.Reset()
144	performRequest(router, "GET", "/notfound")
145	assert.Contains(t, buffer.String(), "404")
146	assert.Contains(t, buffer.String(), "GET")
147	assert.Contains(t, buffer.String(), "/notfound")
148}
149
150func TestLoggerWithFormatter(t *testing.T) {
151	buffer := new(bytes.Buffer)
152
153	d := DefaultWriter
154	DefaultWriter = buffer
155	defer func() {
156		DefaultWriter = d
157	}()
158
159	router := New()
160	router.Use(LoggerWithFormatter(func(param LogFormatterParams) string {
161		return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %#v\n%s",
162			param.TimeStamp.Format("2006/01/02 - 15:04:05"),
163			param.StatusCode,
164			param.Latency,
165			param.ClientIP,
166			param.Method,
167			param.Path,
168			param.ErrorMessage,
169		)
170	}))
171	router.GET("/example", func(c *Context) {})
172	performRequest(router, "GET", "/example?a=100")
173
174	// output test
175	assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
176	assert.Contains(t, buffer.String(), "200")
177	assert.Contains(t, buffer.String(), "GET")
178	assert.Contains(t, buffer.String(), "/example")
179	assert.Contains(t, buffer.String(), "a=100")
180}
181
182func TestLoggerWithConfigFormatting(t *testing.T) {
183	var gotParam LogFormatterParams
184	var gotKeys map[string]interface{}
185	buffer := new(bytes.Buffer)
186
187	router := New()
188	router.Use(LoggerWithConfig(LoggerConfig{
189		Output: buffer,
190		Formatter: func(param LogFormatterParams) string {
191			// for assert test
192			gotParam = param
193
194			return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s",
195				param.TimeStamp.Format("2006/01/02 - 15:04:05"),
196				param.StatusCode,
197				param.Latency,
198				param.ClientIP,
199				param.Method,
200				param.Path,
201				param.ErrorMessage,
202			)
203		},
204	}))
205	router.GET("/example", func(c *Context) {
206		// set dummy ClientIP
207		c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
208		gotKeys = c.Keys
209	})
210	performRequest(router, "GET", "/example?a=100")
211
212	// output test
213	assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
214	assert.Contains(t, buffer.String(), "200")
215	assert.Contains(t, buffer.String(), "GET")
216	assert.Contains(t, buffer.String(), "/example")
217	assert.Contains(t, buffer.String(), "a=100")
218
219	// LogFormatterParams test
220	assert.NotNil(t, gotParam.Request)
221	assert.NotEmpty(t, gotParam.TimeStamp)
222	assert.Equal(t, 200, gotParam.StatusCode)
223	assert.NotEmpty(t, gotParam.Latency)
224	assert.Equal(t, "20.20.20.20", gotParam.ClientIP)
225	assert.Equal(t, "GET", gotParam.Method)
226	assert.Equal(t, "/example?a=100", gotParam.Path)
227	assert.Empty(t, gotParam.ErrorMessage)
228	assert.Equal(t, gotKeys, gotParam.Keys)
229
230}
231
232func TestDefaultLogFormatter(t *testing.T) {
233	timeStamp := time.Unix(1544173902, 0).UTC()
234
235	termFalseParam := LogFormatterParams{
236		TimeStamp:    timeStamp,
237		StatusCode:   200,
238		Latency:      time.Second * 5,
239		ClientIP:     "20.20.20.20",
240		Method:       "GET",
241		Path:         "/",
242		ErrorMessage: "",
243		isTerm:       false,
244	}
245
246	termTrueParam := LogFormatterParams{
247		TimeStamp:    timeStamp,
248		StatusCode:   200,
249		Latency:      time.Second * 5,
250		ClientIP:     "20.20.20.20",
251		Method:       "GET",
252		Path:         "/",
253		ErrorMessage: "",
254		isTerm:       true,
255	}
256	termTrueLongDurationParam := LogFormatterParams{
257		TimeStamp:    timeStamp,
258		StatusCode:   200,
259		Latency:      time.Millisecond * 9876543210,
260		ClientIP:     "20.20.20.20",
261		Method:       "GET",
262		Path:         "/",
263		ErrorMessage: "",
264		isTerm:       true,
265	}
266
267	termFalseLongDurationParam := LogFormatterParams{
268		TimeStamp:    timeStamp,
269		StatusCode:   200,
270		Latency:      time.Millisecond * 9876543210,
271		ClientIP:     "20.20.20.20",
272		Method:       "GET",
273		Path:         "/",
274		ErrorMessage: "",
275		isTerm:       false,
276	}
277
278	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |            5s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseParam))
279	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |    2743h29m3s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseLongDurationParam))
280
281	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|            5s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
282	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|    2743h29m3s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
283
284}
285
286func TestColorForMethod(t *testing.T) {
287	colorForMethod := func(method string) string {
288		p := LogFormatterParams{
289			Method: method,
290		}
291		return p.MethodColor()
292	}
293
294	assert.Equal(t, blue, colorForMethod("GET"), "get should be blue")
295	assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan")
296	assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow")
297	assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red")
298	assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green")
299	assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta")
300	assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white")
301	assert.Equal(t, reset, colorForMethod("TRACE"), "trace is not defined and should be the reset color")
302}
303
304func TestColorForStatus(t *testing.T) {
305	colorForStatus := func(code int) string {
306		p := LogFormatterParams{
307			StatusCode: code,
308		}
309		return p.StatusCodeColor()
310	}
311
312	assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green")
313	assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white")
314	assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow")
315	assert.Equal(t, red, colorForStatus(2), "other things should be red")
316}
317
318func TestResetColor(t *testing.T) {
319	p := LogFormatterParams{}
320	assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
321}
322
323func TestIsOutputColor(t *testing.T) {
324	// test with isTerm flag true.
325	p := LogFormatterParams{
326		isTerm: true,
327	}
328
329	consoleColorMode = autoColor
330	assert.Equal(t, true, p.IsOutputColor())
331
332	ForceConsoleColor()
333	assert.Equal(t, true, p.IsOutputColor())
334
335	DisableConsoleColor()
336	assert.Equal(t, false, p.IsOutputColor())
337
338	// test with isTerm flag false.
339	p = LogFormatterParams{
340		isTerm: false,
341	}
342
343	consoleColorMode = autoColor
344	assert.Equal(t, false, p.IsOutputColor())
345
346	ForceConsoleColor()
347	assert.Equal(t, true, p.IsOutputColor())
348
349	DisableConsoleColor()
350	assert.Equal(t, false, p.IsOutputColor())
351
352	// reset console color mode.
353	consoleColorMode = autoColor
354}
355
356func TestErrorLogger(t *testing.T) {
357	router := New()
358	router.Use(ErrorLogger())
359	router.GET("/error", func(c *Context) {
360		c.Error(errors.New("this is an error")) // nolint: errcheck
361	})
362	router.GET("/abort", func(c *Context) {
363		c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck
364	})
365	router.GET("/print", func(c *Context) {
366		c.Error(errors.New("this is an error")) // nolint: errcheck
367		c.String(http.StatusInternalServerError, "hola!")
368	})
369
370	w := performRequest(router, "GET", "/error")
371	assert.Equal(t, http.StatusOK, w.Code)
372	assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
373
374	w = performRequest(router, "GET", "/abort")
375	assert.Equal(t, http.StatusUnauthorized, w.Code)
376	assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
377
378	w = performRequest(router, "GET", "/print")
379	assert.Equal(t, http.StatusInternalServerError, w.Code)
380	assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
381}
382
383func TestLoggerWithWriterSkippingPaths(t *testing.T) {
384	buffer := new(bytes.Buffer)
385	router := New()
386	router.Use(LoggerWithWriter(buffer, "/skipped"))
387	router.GET("/logged", func(c *Context) {})
388	router.GET("/skipped", func(c *Context) {})
389
390	performRequest(router, "GET", "/logged")
391	assert.Contains(t, buffer.String(), "200")
392
393	buffer.Reset()
394	performRequest(router, "GET", "/skipped")
395	assert.Contains(t, buffer.String(), "")
396}
397
398func TestLoggerWithConfigSkippingPaths(t *testing.T) {
399	buffer := new(bytes.Buffer)
400	router := New()
401	router.Use(LoggerWithConfig(LoggerConfig{
402		Output:    buffer,
403		SkipPaths: []string{"/skipped"},
404	}))
405	router.GET("/logged", func(c *Context) {})
406	router.GET("/skipped", func(c *Context) {})
407
408	performRequest(router, "GET", "/logged")
409	assert.Contains(t, buffer.String(), "200")
410
411	buffer.Reset()
412	performRequest(router, "GET", "/skipped")
413	assert.Contains(t, buffer.String(), "")
414}
415
416func TestDisableConsoleColor(t *testing.T) {
417	New()
418	assert.Equal(t, autoColor, consoleColorMode)
419	DisableConsoleColor()
420	assert.Equal(t, disableColor, consoleColorMode)
421
422	// reset console color mode.
423	consoleColorMode = autoColor
424}
425
426func TestForceConsoleColor(t *testing.T) {
427	New()
428	assert.Equal(t, autoColor, consoleColorMode)
429	ForceConsoleColor()
430	assert.Equal(t, forceColor, consoleColorMode)
431
432	// reset console color mode.
433	consoleColorMode = autoColor
434}
435