veza/veza-backend-api/internal/middleware/validation_test.go
senke 0303d0f02c [BE-SVC-020] be-svc: Implement request validation improvements
- Enhanced error messages in validator with more descriptive and contextual messages
- Added custom validations: slug, phone, date_iso, not_empty
- Created QueryParamValidation middleware for query parameter validation
- Support for validation rules: numeric, integer, min, max, oneof, email, uuid, url
- Improved error messages for all validation tags (40+ tags supported)
- Comprehensive unit tests for query parameter validation
- Better error context and user-friendly messages

Phase: PHASE-6
Priority: P2
Progress: 116/267 (43.45%)
2025-12-24 17:09:54 +01:00

203 lines
4.4 KiB
Go

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