package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) func TestQueryParamValidation_ValidateQueryParams(t *testing.T) { gin.SetMode(gin.TestMode) logger, _ := zap.NewDevelopment() queryValidation := NewQueryParamValidation(logger) tests := []struct { name string url string rules map[string]string expectedStatus int shouldAbort bool }{ { name: "valid numeric params", url: "/test?page=1&limit=10", rules: map[string]string{ "page": "numeric,min=1", "limit": "numeric,min=1,max=100", }, expectedStatus: http.StatusOK, shouldAbort: false, }, { name: "invalid numeric param", url: "/test?page=abc", rules: map[string]string{ "page": "numeric", }, expectedStatus: http.StatusBadRequest, shouldAbort: true, }, { name: "param below minimum", url: "/test?page=0", rules: map[string]string{ "page": "numeric,min=1", }, expectedStatus: http.StatusBadRequest, shouldAbort: true, }, { name: "param above maximum", url: "/test?limit=200", rules: map[string]string{ "limit": "numeric,max=100", }, expectedStatus: http.StatusBadRequest, shouldAbort: true, }, { name: "oneof validation - valid", url: "/test?sort=asc", rules: map[string]string{ "sort": "oneof=asc,desc", }, expectedStatus: http.StatusOK, shouldAbort: false, }, { name: "oneof validation - invalid", url: "/test?sort=invalid", rules: map[string]string{ "sort": "oneof=asc,desc", }, expectedStatus: http.StatusBadRequest, shouldAbort: true, }, { name: "required param missing", url: "/test", rules: map[string]string{ "page": "required,numeric", }, expectedStatus: http.StatusBadRequest, shouldAbort: true, }, { name: "optional param missing", url: "/test", rules: map[string]string{ "page": "numeric", }, expectedStatus: http.StatusOK, shouldAbort: false, }, { name: "valid UUID", url: "/test?id=550e8400-e29b-41d4-a716-446655440000", rules: map[string]string{ "id": "uuid", }, expectedStatus: http.StatusOK, shouldAbort: false, }, { name: "invalid UUID", url: "/test?id=invalid-uuid", rules: map[string]string{ "id": "uuid", }, expectedStatus: http.StatusBadRequest, shouldAbort: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { router := gin.New() router.GET("/test", queryValidation.ValidateQueryParams(tt.rules), func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", tt.url, nil) router.ServeHTTP(w, req) assert.Equal(t, tt.expectedStatus, w.Code) if tt.shouldAbort { assert.Contains(t, w.Body.String(), "Invalid query parameters") } else { assert.Contains(t, w.Body.String(), "ok") } }) } } func TestParseRules(t *testing.T) { tests := []struct { name string ruleStr string expected []rule }{ { name: "single rule", ruleStr: "numeric", expected: []rule{ {name: "numeric", param: ""}, }, }, { name: "rule with param", ruleStr: "min=1", expected: []rule{ {name: "min", param: "1"}, }, }, { name: "multiple rules", ruleStr: "numeric,min=1,max=100", expected: []rule{ {name: "numeric", param: ""}, {name: "min", param: "1"}, {name: "max", param: "100"}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parseRules(tt.ruleStr) require.Equal(t, len(tt.expected), len(result)) for i, expected := range tt.expected { assert.Equal(t, expected.name, result[i].name) assert.Equal(t, expected.param, result[i].param) } }) } } func TestContainsRule(t *testing.T) { tests := []struct { name string ruleStr string ruleName string expected bool }{ {"contains", "numeric,min=1", "min", true}, {"not contains", "numeric,min=1", "max", false}, {"single rule", "numeric", "numeric", true}, {"empty", "", "numeric", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := containsRule(tt.ruleStr, tt.ruleName) assert.Equal(t, tt.expected, result) }) } }