Files
Tosone 7b98fc29c0 refactor(server): simplify handler wrapping and request binding
Remove the unused Wrap/WrapHandle helpers and the HandlerFunc type; route
registration now passes handler method values directly. Drop the
unreachable application/x-www-form-urlencoded and multipart/form-data
branches in bindBody since every bound endpoint is JSON, and merge
adapter.go into request.go. Add tests for the query/param mapping error
paths, the nil-validator path, and non-JSON bodies, raising the package
coverage to 100%.

Verification:
- go build -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./...
- go vet -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/server/
- CI_DATABASE_TYPE=turso go test -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/server/...
- go tool cover -func: pkg/server 100.0% of statements
2026-10-07 21:07:06 +08:00

207 lines
5.6 KiB
Go

// Copyright 2026 sigma
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server_test
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/stretchr/testify/require"
"github.com/go-sigma/sigma/pkg/server"
"github.com/go-sigma/sigma/pkg/server/errcode"
"github.com/go-sigma/sigma/pkg/server/validators"
)
type bindRequest struct {
Source string `json:"source" query:"source" param:"source" validate:"required"`
Body string `json:"body" validate:"required"`
Page *int `json:"page" query:"page"`
}
type sourceRequest struct {
Source string `param:"source" validate:"required"`
}
type emptyRequest struct{}
type badQueryRequest struct {
Bad func() `query:"bad"`
}
type badParamRequest struct {
Bad func() `param:"bad"`
}
type textRequest struct {
Body string `json:"body"`
}
func TestBindRequestBindsBodyQueryAndPathBeforeValidation(t *testing.T) {
setupValidator(t)
router := gin.New()
router.POST("/items/:source", func(c *gin.Context) {
var req bindRequest
require.NoError(t, server.BindRequest(c, &req))
require.Equal(t, "path", req.Source)
require.Equal(t, "payload", req.Body)
require.NotNil(t, req.Page)
require.Equal(t, 2, *req.Page)
c.Status(http.StatusNoContent)
})
body := bytes.NewBufferString(`{"source":"body","body":"payload"}`)
req := httptest.NewRequest(http.MethodPost, "/items/path?source=query&page=2", body)
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusNoContent, rec.Code)
}
func TestBindRequestReturnsMalformedJSONError(t *testing.T) {
setupValidator(t)
router := gin.New()
router.POST("/items/:source", func(c *gin.Context) {
var req bindRequest
require.Error(t, server.BindRequest(c, &req))
c.Status(http.StatusBadRequest)
})
req := httptest.NewRequest(http.MethodPost, "/items/path", bytes.NewBufferString(`{"body":`))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusBadRequest, rec.Code)
}
func TestWrapRequestReturnsBadRequestForValidationError(t *testing.T) {
setupValidator(t)
router := gin.New()
router.GET("/items", server.WrapRequest(func(c *gin.Context, req *sourceRequest) {
t.Fatalf("handler should not be called")
}))
req := httptest.NewRequest(http.MethodGet, "/items", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusBadRequest, rec.Code)
require.Contains(t, rec.Body.String(), errcode.HTTPErrCodeBadRequest.Code)
}
func TestWrapRequestKeepsHandlerOwnedErrorResponse(t *testing.T) {
setupValidator(t)
router := gin.New()
router.GET("/items", server.WrapRequest(func(c *gin.Context, req *emptyRequest) {
errcode.NewHTTPError(c, errcode.HTTPErrCodeNotFound)
}))
req := httptest.NewRequest(http.MethodGet, "/items", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusNotFound, rec.Code)
require.Contains(t, rec.Body.String(), errcode.HTTPErrCodeNotFound.Code)
}
func TestBindRequestReturnsQueryMappingError(t *testing.T) {
setupValidator(t)
router := gin.New()
router.GET("/items", func(c *gin.Context) {
var req badQueryRequest
require.Error(t, server.BindRequest(c, &req))
c.Status(http.StatusBadRequest)
})
req := httptest.NewRequest(http.MethodGet, "/items?bad=value", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusBadRequest, rec.Code)
}
func TestBindRequestReturnsParamMappingError(t *testing.T) {
setupValidator(t)
router := gin.New()
router.GET("/items/:bad", func(c *gin.Context) {
var req badParamRequest
require.Error(t, server.BindRequest(c, &req))
c.Status(http.StatusBadRequest)
})
req := httptest.NewRequest(http.MethodGet, "/items/value", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusBadRequest, rec.Code)
}
func TestBindRequestSkipsValidationWhenValidatorNil(t *testing.T) {
setupValidator(t)
original := binding.Validator
binding.Validator = nil
defer func() { binding.Validator = original }()
router := gin.New()
router.GET("/items", func(c *gin.Context) {
var req sourceRequest
require.NoError(t, server.BindRequest(c, &req))
c.Status(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodGet, "/items", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusNoContent, rec.Code)
}
func TestBindRequestIgnoresNonJSONBody(t *testing.T) {
setupValidator(t)
router := gin.New()
router.POST("/items", func(c *gin.Context) {
var req textRequest
require.NoError(t, server.BindRequest(c, &req))
require.Empty(t, req.Body)
c.Status(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodPost, "/items", bytes.NewBufferString("plain text"))
req.Header.Set("Content-Type", "text/plain")
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusNoContent, rec.Code)
}
func setupValidator(t *testing.T) {
t.Helper()
require.NoError(t, validators.Initialize())
}