ResolveSpec/pkg/restheadspec/query_params_test.go

404 lines
12 KiB
Go

package restheadspec
import (
"testing"
)
// MockRequest implements common.Request interface for testing
type MockRequest struct {
headers map[string]string
queryParams map[string]string
}
func (m *MockRequest) Method() string {
return "GET"
}
func (m *MockRequest) URL() string {
return "http://example.com/test"
}
func (m *MockRequest) Header(key string) string {
return m.headers[key]
}
func (m *MockRequest) AllHeaders() map[string]string {
return m.headers
}
func (m *MockRequest) Body() ([]byte, error) {
return nil, nil
}
func (m *MockRequest) PathParam(key string) string {
return ""
}
func (m *MockRequest) QueryParam(key string) string {
return m.queryParams[key]
}
func (m *MockRequest) AllQueryParams() map[string]string {
return m.queryParams
}
func TestParseOptionsFromQueryParams(t *testing.T) {
handler := NewHandler(nil, nil)
tests := []struct {
name string
queryParams map[string]string
headers map[string]string
validate func(t *testing.T, options ExtendedRequestOptions)
}{
{
name: "Parse custom SQL WHERE from query params",
queryParams: map[string]string{
"x-custom-sql-w-1": `("v_webui_clients".clientstatus = 0 or "v_webui_clients".clientstatus is null)`,
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if options.CustomSQLWhere == "" {
t.Error("Expected CustomSQLWhere to be set from query param")
}
expected := `("v_webui_clients".clientstatus = 0 or "v_webui_clients".clientstatus is null)`
if options.CustomSQLWhere != expected {
t.Errorf("Expected CustomSQLWhere=%q, got %q", expected, options.CustomSQLWhere)
}
},
},
{
name: "Parse sort from query params",
queryParams: map[string]string{
"x-sort": "-applicationdate,name",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if len(options.Sort) != 2 {
t.Errorf("Expected 2 sort options, got %d", len(options.Sort))
return
}
if options.Sort[0].Column != "applicationdate" || options.Sort[0].Direction != "DESC" {
t.Errorf("Expected first sort: applicationdate DESC, got %s %s", options.Sort[0].Column, options.Sort[0].Direction)
}
if options.Sort[1].Column != "name" || options.Sort[1].Direction != "ASC" {
t.Errorf("Expected second sort: name ASC, got %s %s", options.Sort[1].Column, options.Sort[1].Direction)
}
},
},
{
name: "Parse limit and offset from query params",
queryParams: map[string]string{
"x-limit": "100",
"x-offset": "50",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if options.Limit == nil || *options.Limit != 100 {
t.Errorf("Expected limit=100, got %v", options.Limit)
}
if options.Offset == nil || *options.Offset != 50 {
t.Errorf("Expected offset=50, got %v", options.Offset)
}
},
},
{
name: "Parse field filters from query params",
queryParams: map[string]string{
"x-fieldfilter-status": "active",
"x-fieldfilter-type": "user",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if len(options.Filters) != 2 {
t.Errorf("Expected 2 filters, got %d", len(options.Filters))
return
}
// Check that filters were created
foundStatus := false
foundType := false
for _, filter := range options.Filters {
if filter.Column == "status" && filter.Value == "active" && filter.Operator == "eq" {
foundStatus = true
}
if filter.Column == "type" && filter.Value == "user" && filter.Operator == "eq" {
foundType = true
}
}
if !foundStatus {
t.Error("Expected status filter not found")
}
if !foundType {
t.Error("Expected type filter not found")
}
},
},
{
name: "Parse select fields from query params",
queryParams: map[string]string{
"x-select-fields": "id,name,email",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if len(options.Columns) != 3 {
t.Errorf("Expected 3 columns, got %d", len(options.Columns))
return
}
expected := []string{"id", "name", "email"}
for i, col := range expected {
if i >= len(options.Columns) || options.Columns[i] != col {
t.Errorf("Expected column[%d]=%s, got %v", i, col, options.Columns)
}
}
},
},
{
name: "Parse preload from query params",
queryParams: map[string]string{
"x-preload": "posts:title,content|comments",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if len(options.Preload) != 2 {
t.Errorf("Expected 2 preload options, got %d", len(options.Preload))
return
}
// Check first preload (posts with columns)
if options.Preload[0].Relation != "posts" {
t.Errorf("Expected first preload relation=posts, got %s", options.Preload[0].Relation)
}
if len(options.Preload[0].Columns) != 2 {
t.Errorf("Expected 2 columns for posts preload, got %d", len(options.Preload[0].Columns))
}
// Check second preload (comments without columns)
if options.Preload[1].Relation != "comments" {
t.Errorf("Expected second preload relation=comments, got %s", options.Preload[1].Relation)
}
},
},
{
name: "Query params take precedence over headers",
queryParams: map[string]string{
"x-limit": "100",
},
headers: map[string]string{
"X-Limit": "50",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if options.Limit == nil || *options.Limit != 100 {
t.Errorf("Expected query param limit=100 to override header, got %v", options.Limit)
}
},
},
{
name: "Parse search operators from query params",
queryParams: map[string]string{
"x-searchop-contains-name": "john",
"x-searchop-gt-age": "18",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if len(options.Filters) != 2 {
t.Errorf("Expected 2 filters, got %d", len(options.Filters))
return
}
// Check for ILIKE filter
foundContains := false
foundGt := false
for _, filter := range options.Filters {
if filter.Column == "name" && filter.Operator == "ilike" {
foundContains = true
}
if filter.Column == "age" && filter.Operator == "gt" && filter.Value == "18" {
foundGt = true
}
}
if !foundContains {
t.Error("Expected contains filter not found")
}
if !foundGt {
t.Error("Expected gt filter not found")
}
},
},
{
name: "Parse complex example with multiple params",
queryParams: map[string]string{
"x-custom-sql-w-1": `("v_webui_clients".clientstatus = 0)`,
"x-sort": "-applicationdate",
"x-limit": "100",
"x-select-fields": "id,name,status",
"x-fieldfilter-active": "true",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
// Validate CustomSQLWhere
if options.CustomSQLWhere == "" {
t.Error("Expected CustomSQLWhere to be set")
}
// Validate Sort
if len(options.Sort) != 1 || options.Sort[0].Column != "applicationdate" || options.Sort[0].Direction != "DESC" {
t.Errorf("Expected sort by applicationdate DESC, got %v", options.Sort)
}
// Validate Limit
if options.Limit == nil || *options.Limit != 100 {
t.Errorf("Expected limit=100, got %v", options.Limit)
}
// Validate Columns
if len(options.Columns) != 3 {
t.Errorf("Expected 3 columns, got %d", len(options.Columns))
}
// Validate Filters
if len(options.Filters) < 1 {
t.Error("Expected at least 1 filter")
}
},
},
{
name: "Parse distinct flag from query params",
queryParams: map[string]string{
"x-distinct": "true",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if !options.Distinct {
t.Error("Expected Distinct to be true")
}
},
},
{
name: "Parse skip count flag from query params",
queryParams: map[string]string{
"x-skipcount": "true",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if !options.SkipCount {
t.Error("Expected SkipCount to be true")
}
},
},
{
name: "Parse response format from query params",
queryParams: map[string]string{
"x-syncfusion": "true",
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if options.ResponseFormat != "syncfusion" {
t.Errorf("Expected ResponseFormat=syncfusion, got %s", options.ResponseFormat)
}
},
},
{
name: "Parse custom SQL OR from query params",
queryParams: map[string]string{
"x-custom-sql-or": `("field1" = 'value1' OR "field2" = 'value2')`,
},
validate: func(t *testing.T, options ExtendedRequestOptions) {
if options.CustomSQLOr == "" {
t.Error("Expected CustomSQLOr to be set")
}
expected := `("field1" = 'value1' OR "field2" = 'value2')`
if options.CustomSQLOr != expected {
t.Errorf("Expected CustomSQLOr=%q, got %q", expected, options.CustomSQLOr)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create mock request
req := &MockRequest{
headers: tt.headers,
queryParams: tt.queryParams,
}
if req.headers == nil {
req.headers = make(map[string]string)
}
if req.queryParams == nil {
req.queryParams = make(map[string]string)
}
// Parse options
options := handler.parseOptionsFromHeaders(req, nil)
// Validate
tt.validate(t, options)
})
}
}
func TestQueryParamsWithURLEncoding(t *testing.T) {
handler := NewHandler(nil, nil)
// Test with URL-encoded query parameter (like the user's example)
req := &MockRequest{
headers: make(map[string]string),
queryParams: map[string]string{
// URL-encoded version of the SQL WHERE clause
"x-custom-sql-w-1": `("v_webui_clients".clientstatus = 0 or "v_webui_clients".clientstatus is null) and ("v_webui_clients".inactive = 0 or "v_webui_clients".inactive is null)`,
},
}
options := handler.parseOptionsFromHeaders(req, nil)
if options.CustomSQLWhere == "" {
t.Error("Expected CustomSQLWhere to be set from URL-encoded query param")
}
// The SQL should contain the expected conditions
if !contains(options.CustomSQLWhere, "clientstatus") {
t.Error("Expected CustomSQLWhere to contain 'clientstatus'")
}
if !contains(options.CustomSQLWhere, "inactive") {
t.Error("Expected CustomSQLWhere to contain 'inactive'")
}
}
func TestHeadersAndQueryParamsCombined(t *testing.T) {
handler := NewHandler(nil, nil)
// Test that headers and query params can work together
req := &MockRequest{
headers: map[string]string{
"X-Select-Fields": "id,name",
"X-Limit": "50",
},
queryParams: map[string]string{
"x-sort": "-created_at",
"x-offset": "10",
// This should override the header value
"x-limit": "100",
},
}
options := handler.parseOptionsFromHeaders(req, nil)
// Verify columns from header
if len(options.Columns) != 2 {
t.Errorf("Expected 2 columns from header, got %d", len(options.Columns))
}
// Verify sort from query param
if len(options.Sort) != 1 || options.Sort[0].Column != "created_at" {
t.Errorf("Expected sort from query param, got %v", options.Sort)
}
// Verify offset from query param
if options.Offset == nil || *options.Offset != 10 {
t.Errorf("Expected offset=10 from query param, got %v", options.Offset)
}
// Verify limit from query param (should override header)
if options.Limit == nil {
t.Error("Expected limit to be set from query param")
} else if *options.Limit != 100 {
t.Errorf("Expected limit=100 from query param (overriding header), got %d", *options.Limit)
}
}
// Helper function to check if a string contains a substring
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsHelper(s, substr))
}
func containsHelper(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}