mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-06 14:26:22 +00:00
550 lines
13 KiB
Go
550 lines
13 KiB
Go
package funcspec
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestParseParameters tests the comprehensive parameter parsing
|
|
func TestParseParameters(t *testing.T) {
|
|
handler := NewHandler(&MockDatabase{})
|
|
|
|
tests := []struct {
|
|
name string
|
|
queryParams map[string]string
|
|
headers map[string]string
|
|
validate func(t *testing.T, params *RequestParameters)
|
|
}{
|
|
{
|
|
name: "Parse field selection",
|
|
headers: map[string]string{
|
|
"X-Select-Fields": "id,name,email",
|
|
"X-Not-Select-Fields": "password,ssn",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if len(params.SelectFields) != 3 {
|
|
t.Errorf("Expected 3 select fields, got %d", len(params.SelectFields))
|
|
}
|
|
if len(params.NotSelectFields) != 2 {
|
|
t.Errorf("Expected 2 not-select fields, got %d", len(params.NotSelectFields))
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse distinct flag",
|
|
headers: map[string]string{
|
|
"X-Distinct": "true",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if !params.Distinct {
|
|
t.Error("Expected Distinct to be true")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse field filters",
|
|
headers: map[string]string{
|
|
"X-FieldFilter-Status": "active",
|
|
"X-FieldFilter-Type": "admin",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if len(params.FieldFilters) != 2 {
|
|
t.Errorf("Expected 2 field filters, got %d", len(params.FieldFilters))
|
|
}
|
|
if params.FieldFilters["status"] != "active" {
|
|
t.Errorf("Expected status filter=active, got %s", params.FieldFilters["status"])
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse search filters",
|
|
headers: map[string]string{
|
|
"X-SearchFilter-Name": "john",
|
|
"X-SearchFilter-Email": "test",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if len(params.SearchFilters) != 2 {
|
|
t.Errorf("Expected 2 search filters, got %d", len(params.SearchFilters))
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse sort columns",
|
|
queryParams: map[string]string{
|
|
"sort": "-created_at,name",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if params.SortColumns != "-created_at,name" {
|
|
t.Errorf("Expected sort columns=-created_at,name, got %s", params.SortColumns)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse limit and offset",
|
|
queryParams: map[string]string{
|
|
"limit": "100",
|
|
"offset": "50",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if params.Limit != 100 {
|
|
t.Errorf("Expected limit=100, got %d", params.Limit)
|
|
}
|
|
if params.Offset != 50 {
|
|
t.Errorf("Expected offset=50, got %d", params.Offset)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse skip count",
|
|
headers: map[string]string{
|
|
"X-SkipCount": "true",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if !params.SkipCount {
|
|
t.Error("Expected SkipCount to be true")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse response format - syncfusion",
|
|
headers: map[string]string{
|
|
"X-Syncfusion": "true",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if params.ResponseFormat != "syncfusion" {
|
|
t.Errorf("Expected ResponseFormat=syncfusion, got %s", params.ResponseFormat)
|
|
}
|
|
if !params.ComplexAPI {
|
|
t.Error("Expected ComplexAPI to be true for syncfusion format")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse response format - detail",
|
|
headers: map[string]string{
|
|
"X-DetailAPI": "true",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if params.ResponseFormat != "detail" {
|
|
t.Errorf("Expected ResponseFormat=detail, got %s", params.ResponseFormat)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse simple API",
|
|
headers: map[string]string{
|
|
"X-SimpleAPI": "true",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if params.ResponseFormat != "simple" {
|
|
t.Errorf("Expected ResponseFormat=simple, got %s", params.ResponseFormat)
|
|
}
|
|
if params.ComplexAPI {
|
|
t.Error("Expected ComplexAPI to be false for simple API")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse custom SQL WHERE",
|
|
headers: map[string]string{
|
|
"X-Custom-SQL-W": "status = 'active' AND deleted = false",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if params.CustomSQLWhere == "" {
|
|
t.Error("Expected CustomSQLWhere to be set")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse search operators - AND",
|
|
headers: map[string]string{
|
|
"X-SearchOp-Eq-Name": "john",
|
|
"X-SearchOp-Gt-Age": "18",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if len(params.SearchOps) != 2 {
|
|
t.Errorf("Expected 2 search operators, got %d", len(params.SearchOps))
|
|
}
|
|
if op, exists := params.SearchOps["name"]; exists {
|
|
if op.Operator != "eq" {
|
|
t.Errorf("Expected operator=eq for name, got %s", op.Operator)
|
|
}
|
|
if op.Logic != "AND" {
|
|
t.Errorf("Expected logic=AND, got %s", op.Logic)
|
|
}
|
|
} else {
|
|
t.Error("Expected name search operator to exist")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "Parse search operators - OR",
|
|
headers: map[string]string{
|
|
"X-SearchOr-Like-Description": "test",
|
|
},
|
|
validate: func(t *testing.T, params *RequestParameters) {
|
|
if op, exists := params.SearchOps["description"]; exists {
|
|
if op.Logic != "OR" {
|
|
t.Errorf("Expected logic=OR, got %s", op.Logic)
|
|
}
|
|
} else {
|
|
t.Error("Expected description search operator to exist")
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := createTestRequest("GET", "/test", tt.queryParams, tt.headers, nil)
|
|
params := handler.ParseParameters(req)
|
|
|
|
if tt.validate != nil {
|
|
tt.validate(t, params)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBuildFilterCondition tests the filter condition builder
|
|
func TestBuildFilterCondition(t *testing.T) {
|
|
handler := NewHandler(&MockDatabase{})
|
|
|
|
tests := []struct {
|
|
name string
|
|
colName string
|
|
operator FilterOperator
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Equals operator - numeric",
|
|
colName: "age",
|
|
operator: FilterOperator{
|
|
Operator: "eq",
|
|
Value: "25",
|
|
Logic: "AND",
|
|
},
|
|
expected: "age = 25",
|
|
},
|
|
{
|
|
name: "Equals operator - string",
|
|
colName: "name",
|
|
operator: FilterOperator{
|
|
Operator: "eq",
|
|
Value: "john",
|
|
Logic: "AND",
|
|
},
|
|
expected: "name = 'john'",
|
|
},
|
|
{
|
|
name: "Not equals operator",
|
|
colName: "status",
|
|
operator: FilterOperator{
|
|
Operator: "neq",
|
|
Value: "inactive",
|
|
Logic: "AND",
|
|
},
|
|
expected: "status != 'inactive'",
|
|
},
|
|
{
|
|
name: "Greater than operator",
|
|
colName: "age",
|
|
operator: FilterOperator{
|
|
Operator: "gt",
|
|
Value: "18",
|
|
Logic: "AND",
|
|
},
|
|
expected: "age > 18",
|
|
},
|
|
{
|
|
name: "Less than operator",
|
|
colName: "price",
|
|
operator: FilterOperator{
|
|
Operator: "lt",
|
|
Value: "100",
|
|
Logic: "AND",
|
|
},
|
|
expected: "price < 100",
|
|
},
|
|
{
|
|
name: "Contains operator",
|
|
colName: "description",
|
|
operator: FilterOperator{
|
|
Operator: "contains",
|
|
Value: "test",
|
|
Logic: "AND",
|
|
},
|
|
expected: "description ILIKE '%test%'",
|
|
},
|
|
{
|
|
name: "Starts with operator",
|
|
colName: "name",
|
|
operator: FilterOperator{
|
|
Operator: "startswith",
|
|
Value: "john",
|
|
Logic: "AND",
|
|
},
|
|
expected: "name ILIKE 'john%'",
|
|
},
|
|
{
|
|
name: "Ends with operator",
|
|
colName: "email",
|
|
operator: FilterOperator{
|
|
Operator: "endswith",
|
|
Value: "@example.com",
|
|
Logic: "AND",
|
|
},
|
|
expected: "email ILIKE '%@example.com'",
|
|
},
|
|
{
|
|
name: "Between operator",
|
|
colName: "age",
|
|
operator: FilterOperator{
|
|
Operator: "between",
|
|
Value: "18,65",
|
|
Logic: "AND",
|
|
},
|
|
expected: "age > 18 AND age < 65",
|
|
},
|
|
{
|
|
name: "IN operator",
|
|
colName: "status",
|
|
operator: FilterOperator{
|
|
Operator: "in",
|
|
Value: "active,pending,approved",
|
|
Logic: "AND",
|
|
},
|
|
expected: "status IN ('active', 'pending', 'approved')",
|
|
},
|
|
{
|
|
name: "IS NULL operator",
|
|
colName: "deleted_at",
|
|
operator: FilterOperator{
|
|
Operator: "null",
|
|
Value: "",
|
|
Logic: "AND",
|
|
},
|
|
expected: "(deleted_at IS NULL OR deleted_at = '')",
|
|
},
|
|
{
|
|
name: "IS NOT NULL operator",
|
|
colName: "created_at",
|
|
operator: FilterOperator{
|
|
Operator: "notnull",
|
|
Value: "",
|
|
Logic: "AND",
|
|
},
|
|
expected: "(created_at IS NOT NULL AND created_at != '')",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := handler.buildFilterCondition(tt.colName, tt.operator)
|
|
if result != tt.expected {
|
|
t.Errorf("Expected: %s\nGot: %s", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestApplyFilters tests the filter application to SQL queries
|
|
func TestApplyFilters(t *testing.T) {
|
|
handler := NewHandler(&MockDatabase{})
|
|
|
|
tests := []struct {
|
|
name string
|
|
sqlQuery string
|
|
params *RequestParameters
|
|
expectedSQL string
|
|
shouldContain []string
|
|
}{
|
|
{
|
|
name: "Apply field filter",
|
|
sqlQuery: "SELECT * FROM users",
|
|
params: &RequestParameters{
|
|
FieldFilters: map[string]string{
|
|
"status": "active",
|
|
},
|
|
},
|
|
shouldContain: []string{"WHERE", "status"},
|
|
},
|
|
{
|
|
name: "Apply search filter",
|
|
sqlQuery: "SELECT * FROM users",
|
|
params: &RequestParameters{
|
|
SearchFilters: map[string]string{
|
|
"name": "john",
|
|
},
|
|
},
|
|
shouldContain: []string{"WHERE", "name", "ILIKE"},
|
|
},
|
|
{
|
|
name: "Apply search operators",
|
|
sqlQuery: "SELECT * FROM users",
|
|
params: &RequestParameters{
|
|
SearchOps: map[string]FilterOperator{
|
|
"age": {
|
|
Operator: "gt",
|
|
Value: "18",
|
|
Logic: "AND",
|
|
},
|
|
},
|
|
},
|
|
shouldContain: []string{"WHERE", "age", ">", "18"},
|
|
},
|
|
{
|
|
name: "Apply custom SQL WHERE",
|
|
sqlQuery: "SELECT * FROM users",
|
|
params: &RequestParameters{
|
|
CustomSQLWhere: "deleted = false",
|
|
},
|
|
shouldContain: []string{"WHERE", "deleted"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := handler.ApplyFilters(tt.sqlQuery, tt.params)
|
|
|
|
for _, expected := range tt.shouldContain {
|
|
if !strings.Contains(result, expected) {
|
|
t.Errorf("Expected SQL to contain %q, got: %s", expected, result)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestApplyDistinct tests DISTINCT application
|
|
func TestApplyDistinct(t *testing.T) {
|
|
handler := NewHandler(&MockDatabase{})
|
|
|
|
tests := []struct {
|
|
name string
|
|
sqlQuery string
|
|
distinct bool
|
|
shouldHave string
|
|
}{
|
|
{
|
|
name: "Apply DISTINCT",
|
|
sqlQuery: "SELECT id, name FROM users",
|
|
distinct: true,
|
|
shouldHave: "SELECT DISTINCT",
|
|
},
|
|
{
|
|
name: "Do not apply DISTINCT",
|
|
sqlQuery: "SELECT id, name FROM users",
|
|
distinct: false,
|
|
shouldHave: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
params := &RequestParameters{Distinct: tt.distinct}
|
|
result := handler.ApplyDistinct(tt.sqlQuery, params)
|
|
|
|
if tt.shouldHave != "" {
|
|
if !strings.Contains(result, tt.shouldHave) {
|
|
t.Errorf("Expected SQL to contain %q, got: %s", tt.shouldHave, result)
|
|
}
|
|
} else {
|
|
// Should not have DISTINCT when not requested
|
|
if strings.Contains(result, "DISTINCT") && !tt.distinct {
|
|
t.Errorf("SQL should not contain DISTINCT when not requested: %s", result)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestParseCommaSeparated tests comma-separated value parsing
|
|
func TestParseCommaSeparated(t *testing.T) {
|
|
handler := NewHandler(&MockDatabase{})
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Simple comma-separated",
|
|
input: "id,name,email",
|
|
expected: []string{"id", "name", "email"},
|
|
},
|
|
{
|
|
name: "With spaces",
|
|
input: "id, name, email",
|
|
expected: []string{"id", "name", "email"},
|
|
},
|
|
{
|
|
name: "Empty string",
|
|
input: "",
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "Single value",
|
|
input: "id",
|
|
expected: []string{"id"},
|
|
},
|
|
{
|
|
name: "With extra commas",
|
|
input: "id,,name,,email",
|
|
expected: []string{"id", "name", "email"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := handler.parseCommaSeparated(tt.input)
|
|
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d values, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
|
|
for i, expected := range tt.expected {
|
|
if result[i] != expected {
|
|
t.Errorf("Expected value %d to be %s, got %s", i, expected, result[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSqlQryWhereOr tests OR WHERE clause manipulation
|
|
func TestSqlQryWhereOr(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
sqlQuery string
|
|
condition string
|
|
shouldContain []string
|
|
}{
|
|
{
|
|
name: "Add WHERE with OR to query without WHERE",
|
|
sqlQuery: "SELECT * FROM users",
|
|
condition: "status = 'inactive'",
|
|
shouldContain: []string{"WHERE", "status = 'inactive'"},
|
|
},
|
|
{
|
|
name: "Add OR to query with existing WHERE",
|
|
sqlQuery: "SELECT * FROM users WHERE id > 0",
|
|
condition: "status = 'inactive'",
|
|
shouldContain: []string{"WHERE", "OR", "(status = 'inactive')"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := sqlQryWhereOr(tt.sqlQuery, tt.condition)
|
|
|
|
for _, expected := range tt.shouldContain {
|
|
if !strings.Contains(result, expected) {
|
|
t.Errorf("Expected SQL to contain %q, got: %s", expected, result)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|