gin/logger_test.go
Flc゛ 41d8591eb1
refactor(context): refactor Keys type to map[any]any (#3963)
* refactor(context): refactor keys to `map[any]any`

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

* refactor(context): refactor keys to `map[any]any`

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

* style(context): remove empty lines before GetInt16, GetIntSlice, and GetStringMapString methods

- Remove unnecessary empty lines in the context.go file
- Improve code readability and consistency

Signed-off-by: flc1125 <four_leaf_clover@foxmail.com>

* refactor(context): simplify GetStringSlice function

- Replace manual type assertion with generic getTyped function
- Reduce code duplication and improve type safety

Signed-off-by: flc1125 <four_leaf_clover@foxmail.com>

* test(context): improve context.Set and context.Get tests

- Split existing test into separate functions for different scenarios
- Add test for setting and getting values with any key type
- Add test for handling non-comparable keys
- Improve assertions to check for key existence and value correctness

Signed-off-by: flc1125 <four_leaf_clover@foxmail.com>

* refactor(context): replace fmt.Errorf with fmt.Sprintf in panic message

* test(context): remove trailing hyphen from context_test.go

* refactor(context): improve error message for missing key in context

- Remove unnecessary quotes around the key in the error message
- Simplify the error message format for better readability

* test(context): improve panic test message for non-existent key

---------

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>
Signed-off-by: flc1125 <four_leaf_clover@foxmail.com>
2025-05-26 23:15:14 +08:00

457 lines
14 KiB
Go

// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"errors"
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func init() {
SetMode(TestMode)
}
func TestLogger(t *testing.T) {
buffer := new(strings.Builder)
router := New()
router.Use(LoggerWithWriter(buffer))
router.GET("/example", func(c *Context) {})
router.POST("/example", func(c *Context) {})
router.PUT("/example", func(c *Context) {})
router.DELETE("/example", func(c *Context) {})
router.PATCH("/example", func(c *Context) {})
router.HEAD("/example", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {})
PerformRequest(router, http.MethodGet, "/example?a=100")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodGet)
assert.Contains(t, buffer.String(), "/example")
assert.Contains(t, buffer.String(), "a=100")
// I wrote these first (extending the above) but then realized they are more
// like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go.
buffer.Reset()
PerformRequest(router, http.MethodPost, "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodPost)
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, http.MethodPut, "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodPut)
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, http.MethodDelete, "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodDelete)
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, "PATCH", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, "HEAD", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, "OPTIONS", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, http.MethodGet, "/notfound")
assert.Contains(t, buffer.String(), "404")
assert.Contains(t, buffer.String(), http.MethodGet)
assert.Contains(t, buffer.String(), "/notfound")
}
func TestLoggerWithConfig(t *testing.T) {
buffer := new(strings.Builder)
router := New()
router.Use(LoggerWithConfig(LoggerConfig{Output: buffer}))
router.GET("/example", func(c *Context) {})
router.POST("/example", func(c *Context) {})
router.PUT("/example", func(c *Context) {})
router.DELETE("/example", func(c *Context) {})
router.PATCH("/example", func(c *Context) {})
router.HEAD("/example", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {})
PerformRequest(router, http.MethodGet, "/example?a=100")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodGet)
assert.Contains(t, buffer.String(), "/example")
assert.Contains(t, buffer.String(), "a=100")
// I wrote these first (extending the above) but then realized they are more
// like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go.
buffer.Reset()
PerformRequest(router, http.MethodPost, "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodPost)
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, http.MethodPut, "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodPut)
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, http.MethodDelete, "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodDelete)
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, "PATCH", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, "HEAD", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, "OPTIONS", "/example")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example")
buffer.Reset()
PerformRequest(router, http.MethodGet, "/notfound")
assert.Contains(t, buffer.String(), "404")
assert.Contains(t, buffer.String(), http.MethodGet)
assert.Contains(t, buffer.String(), "/notfound")
}
func TestLoggerWithFormatter(t *testing.T) {
buffer := new(strings.Builder)
d := DefaultWriter
DefaultWriter = buffer
defer func() {
DefaultWriter = d
}()
router := New()
router.Use(LoggerWithFormatter(func(param LogFormatterParams) string {
return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %#v\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.StatusCode,
param.Latency,
param.ClientIP,
param.Method,
param.Path,
param.ErrorMessage,
)
}))
router.GET("/example", func(c *Context) {})
PerformRequest(router, http.MethodGet, "/example?a=100")
// output test
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodGet)
assert.Contains(t, buffer.String(), "/example")
assert.Contains(t, buffer.String(), "a=100")
}
func TestLoggerWithConfigFormatting(t *testing.T) {
var gotParam LogFormatterParams
var gotKeys map[any]any
buffer := new(strings.Builder)
router := New()
router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs()
router.Use(LoggerWithConfig(LoggerConfig{
Output: buffer,
Formatter: func(param LogFormatterParams) string {
// for assert test
gotParam = param
return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.StatusCode,
param.Latency,
param.ClientIP,
param.Method,
param.Path,
param.ErrorMessage,
)
},
}))
router.GET("/example", func(c *Context) {
// set dummy ClientIP
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
gotKeys = c.Keys
time.Sleep(time.Millisecond)
})
PerformRequest(router, http.MethodGet, "/example?a=100")
// output test
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), http.MethodGet)
assert.Contains(t, buffer.String(), "/example")
assert.Contains(t, buffer.String(), "a=100")
// LogFormatterParams test
assert.NotNil(t, gotParam.Request)
assert.NotEmpty(t, gotParam.TimeStamp)
assert.Equal(t, 200, gotParam.StatusCode)
assert.NotEmpty(t, gotParam.Latency)
assert.Equal(t, "20.20.20.20", gotParam.ClientIP)
assert.Equal(t, http.MethodGet, gotParam.Method)
assert.Equal(t, "/example?a=100", gotParam.Path)
assert.Empty(t, gotParam.ErrorMessage)
assert.Equal(t, gotKeys, gotParam.Keys)
}
func TestDefaultLogFormatter(t *testing.T) {
timeStamp := time.Unix(1544173902, 0).UTC()
termFalseParam := LogFormatterParams{
TimeStamp: timeStamp,
StatusCode: 200,
Latency: time.Second * 5,
ClientIP: "20.20.20.20",
Method: http.MethodGet,
Path: "/",
ErrorMessage: "",
isTerm: false,
}
termTrueParam := LogFormatterParams{
TimeStamp: timeStamp,
StatusCode: 200,
Latency: time.Second * 5,
ClientIP: "20.20.20.20",
Method: http.MethodGet,
Path: "/",
ErrorMessage: "",
isTerm: true,
}
termTrueLongDurationParam := LogFormatterParams{
TimeStamp: timeStamp,
StatusCode: 200,
Latency: time.Millisecond * 9876543210,
ClientIP: "20.20.20.20",
Method: http.MethodGet,
Path: "/",
ErrorMessage: "",
isTerm: true,
}
termFalseLongDurationParam := LogFormatterParams{
TimeStamp: timeStamp,
StatusCode: 200,
Latency: time.Millisecond * 9876543210,
ClientIP: "20.20.20.20",
Method: http.MethodGet,
Path: "/",
ErrorMessage: "",
isTerm: false,
}
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam))
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam))
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))
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))
}
func TestColorForMethod(t *testing.T) {
colorForMethod := func(method string) string {
p := LogFormatterParams{
Method: method,
}
return p.MethodColor()
}
assert.Equal(t, blue, colorForMethod(http.MethodGet), "get should be blue")
assert.Equal(t, cyan, colorForMethod(http.MethodPost), "post should be cyan")
assert.Equal(t, yellow, colorForMethod(http.MethodPut), "put should be yellow")
assert.Equal(t, red, colorForMethod(http.MethodDelete), "delete should be red")
assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green")
assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta")
assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white")
assert.Equal(t, reset, colorForMethod("TRACE"), "trace is not defined and should be the reset color")
}
func TestColorForStatus(t *testing.T) {
colorForStatus := func(code int) string {
p := LogFormatterParams{
StatusCode: code,
}
return p.StatusCodeColor()
}
assert.Equal(t, white, colorForStatus(http.StatusContinue), "1xx should be white")
assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green")
assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white")
assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow")
assert.Equal(t, red, colorForStatus(2), "other things should be red")
}
func TestResetColor(t *testing.T) {
p := LogFormatterParams{}
assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
}
func TestIsOutputColor(t *testing.T) {
// test with isTerm flag true.
p := LogFormatterParams{
isTerm: true,
}
consoleColorMode = autoColor
assert.True(t, p.IsOutputColor())
ForceConsoleColor()
assert.True(t, p.IsOutputColor())
DisableConsoleColor()
assert.False(t, p.IsOutputColor())
// test with isTerm flag false.
p = LogFormatterParams{
isTerm: false,
}
consoleColorMode = autoColor
assert.False(t, p.IsOutputColor())
ForceConsoleColor()
assert.True(t, p.IsOutputColor())
DisableConsoleColor()
assert.False(t, p.IsOutputColor())
// reset console color mode.
consoleColorMode = autoColor
}
func TestErrorLogger(t *testing.T) {
router := New()
router.Use(ErrorLogger())
router.GET("/error", func(c *Context) {
c.Error(errors.New("this is an error")) //nolint: errcheck
})
router.GET("/abort", func(c *Context) {
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) //nolint: errcheck
})
router.GET("/print", func(c *Context) {
c.Error(errors.New("this is an error")) //nolint: errcheck
c.String(http.StatusInternalServerError, "hola!")
})
w := PerformRequest(router, http.MethodGet, "/error")
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, "{\"error\":\"this is an error\"}", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/abort")
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.JSONEq(t, "{\"error\":\"no authorized\"}", w.Body.String())
w = PerformRequest(router, http.MethodGet, "/print")
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
}
func TestLoggerWithWriterSkippingPaths(t *testing.T) {
buffer := new(strings.Builder)
router := New()
router.Use(LoggerWithWriter(buffer, "/skipped"))
router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {})
PerformRequest(router, http.MethodGet, "/logged")
assert.Contains(t, buffer.String(), "200")
buffer.Reset()
PerformRequest(router, http.MethodGet, "/skipped")
assert.Contains(t, buffer.String(), "")
}
func TestLoggerWithConfigSkippingPaths(t *testing.T) {
buffer := new(strings.Builder)
router := New()
router.Use(LoggerWithConfig(LoggerConfig{
Output: buffer,
SkipPaths: []string{"/skipped"},
}))
router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {})
PerformRequest(router, http.MethodGet, "/logged")
assert.Contains(t, buffer.String(), "200")
buffer.Reset()
PerformRequest(router, http.MethodGet, "/skipped")
assert.Contains(t, buffer.String(), "")
}
func TestLoggerWithConfigSkipper(t *testing.T) {
buffer := new(strings.Builder)
router := New()
router.Use(LoggerWithConfig(LoggerConfig{
Output: buffer,
Skip: func(c *Context) bool {
return c.Writer.Status() == http.StatusNoContent
},
}))
router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) })
router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) })
PerformRequest(router, http.MethodGet, "/logged")
assert.Contains(t, buffer.String(), "200")
buffer.Reset()
PerformRequest(router, http.MethodGet, "/skipped")
assert.Contains(t, buffer.String(), "")
}
func TestDisableConsoleColor(t *testing.T) {
New()
assert.Equal(t, autoColor, consoleColorMode)
DisableConsoleColor()
assert.Equal(t, disableColor, consoleColorMode)
// reset console color mode.
consoleColorMode = autoColor
}
func TestForceConsoleColor(t *testing.T) {
New()
assert.Equal(t, autoColor, consoleColorMode)
ForceConsoleColor()
assert.Equal(t, forceColor, consoleColorMode)
// reset console color mode.
consoleColorMode = autoColor
}