mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-31 00:34:25 +00:00
Compare commits
6 Commits
v1.0.2
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be38341383 | ||
|
|
fab744b878 | ||
|
|
5ad2bd3a78 | ||
|
|
333fe158e9 | ||
|
|
2a2d351ad4 | ||
|
|
e918c49b84 |
193
pkg/restheadspec/empty_result_test.go
Normal file
193
pkg/restheadspec/empty_result_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package restheadspec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
)
|
||||
|
||||
// Test that normalizeResultArray returns empty array when no records found without ID
|
||||
func TestNormalizeResultArray_EmptyArrayWhenNoID(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
shouldBeEmptyArr bool
|
||||
}{
|
||||
{
|
||||
name: "nil should return empty array",
|
||||
input: nil,
|
||||
shouldBeEmptyArr: true,
|
||||
},
|
||||
{
|
||||
name: "empty slice should return empty array",
|
||||
input: []*EmptyTestModel{},
|
||||
shouldBeEmptyArr: true,
|
||||
},
|
||||
{
|
||||
name: "single element should return the element",
|
||||
input: []*EmptyTestModel{{ID: 1, Name: "test"}},
|
||||
shouldBeEmptyArr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple elements should return the slice",
|
||||
input: []*EmptyTestModel{
|
||||
{ID: 1, Name: "test1"},
|
||||
{ID: 2, Name: "test2"},
|
||||
},
|
||||
shouldBeEmptyArr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := handler.normalizeResultArray(tt.input)
|
||||
|
||||
// For cases that should return empty array
|
||||
if tt.shouldBeEmptyArr {
|
||||
emptyArr, ok := result.([]interface{})
|
||||
if !ok {
|
||||
t.Errorf("Expected empty array []interface{}{}, got %T: %v", result, result)
|
||||
return
|
||||
}
|
||||
if len(emptyArr) != 0 {
|
||||
t.Errorf("Expected empty array with length 0, got length %d", len(emptyArr))
|
||||
}
|
||||
|
||||
// Verify it serializes to [] and not null
|
||||
jsonBytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to marshal result: %v", err)
|
||||
return
|
||||
}
|
||||
if string(jsonBytes) != "[]" {
|
||||
t.Errorf("Expected JSON '[]', got '%s'", string(jsonBytes))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test that sendFormattedResponse adds X-No-Data-Found header
|
||||
func TestSendFormattedResponse_NoDataFoundHeader(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Mock ResponseWriter
|
||||
mockWriter := &MockTestResponseWriter{
|
||||
headers: make(map[string]string),
|
||||
}
|
||||
|
||||
metadata := &common.Metadata{
|
||||
Total: 0,
|
||||
Count: 0,
|
||||
Filtered: 0,
|
||||
Limit: 10,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
options := ExtendedRequestOptions{
|
||||
RequestOptions: common.RequestOptions{},
|
||||
}
|
||||
|
||||
// Test with empty data
|
||||
emptyData := []interface{}{}
|
||||
handler.sendFormattedResponse(mockWriter, emptyData, metadata, options)
|
||||
|
||||
// Check if X-No-Data-Found header was set
|
||||
if mockWriter.headers["X-No-Data-Found"] != "true" {
|
||||
t.Errorf("Expected X-No-Data-Found header to be 'true', got '%s'", mockWriter.headers["X-No-Data-Found"])
|
||||
}
|
||||
|
||||
// Verify the body is an empty array
|
||||
if mockWriter.body == nil {
|
||||
t.Error("Expected body to be set, got nil")
|
||||
} else {
|
||||
bodyBytes, err := json.Marshal(mockWriter.body)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to marshal body: %v", err)
|
||||
}
|
||||
// The body should be wrapped in a Response object with "data" field
|
||||
bodyStr := string(bodyBytes)
|
||||
if !strings.Contains(bodyStr, `"data":[]`) && !strings.Contains(bodyStr, `"result":[]`) {
|
||||
t.Errorf("Expected body to contain empty array, got: %s", bodyStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that sendResponseWithOptions adds X-No-Data-Found header
|
||||
func TestSendResponseWithOptions_NoDataFoundHeader(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
// Mock ResponseWriter
|
||||
mockWriter := &MockTestResponseWriter{
|
||||
headers: make(map[string]string),
|
||||
}
|
||||
|
||||
metadata := &common.Metadata{}
|
||||
options := &ExtendedRequestOptions{}
|
||||
|
||||
// Test with nil data
|
||||
handler.sendResponseWithOptions(mockWriter, nil, metadata, options)
|
||||
|
||||
// Check if X-No-Data-Found header was set
|
||||
if mockWriter.headers["X-No-Data-Found"] != "true" {
|
||||
t.Errorf("Expected X-No-Data-Found header to be 'true', got '%s'", mockWriter.headers["X-No-Data-Found"])
|
||||
}
|
||||
|
||||
// Check status code is 200
|
||||
if mockWriter.statusCode != 200 {
|
||||
t.Errorf("Expected status code 200, got %d", mockWriter.statusCode)
|
||||
}
|
||||
|
||||
// Verify the body is an empty array
|
||||
if mockWriter.body == nil {
|
||||
t.Error("Expected body to be set, got nil")
|
||||
} else {
|
||||
bodyBytes, err := json.Marshal(mockWriter.body)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to marshal body: %v", err)
|
||||
}
|
||||
bodyStr := string(bodyBytes)
|
||||
if bodyStr != "[]" {
|
||||
t.Errorf("Expected body to be '[]', got: %s", bodyStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MockTestResponseWriter for testing
|
||||
type MockTestResponseWriter struct {
|
||||
headers map[string]string
|
||||
statusCode int
|
||||
body interface{}
|
||||
}
|
||||
|
||||
func (m *MockTestResponseWriter) SetHeader(key, value string) {
|
||||
m.headers[key] = value
|
||||
}
|
||||
|
||||
func (m *MockTestResponseWriter) WriteHeader(statusCode int) {
|
||||
m.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (m *MockTestResponseWriter) Write(data []byte) (int, error) {
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
func (m *MockTestResponseWriter) WriteJSON(data interface{}) error {
|
||||
m.body = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockTestResponseWriter) UnderlyingResponseWriter() http.ResponseWriter {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyTestModel for testing
|
||||
type EmptyTestModel struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
@@ -2143,12 +2143,22 @@ func (h *Handler) sendResponse(w common.ResponseWriter, data interface{}, metada
|
||||
// sendResponseWithOptions sends a response with optional formatting
|
||||
func (h *Handler) sendResponseWithOptions(w common.ResponseWriter, data interface{}, metadata *common.Metadata, options *ExtendedRequestOptions) {
|
||||
w.SetHeader("Content-Type", "application/json")
|
||||
|
||||
// Handle nil data - convert to empty array
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
w.WriteHeader(http.StatusPartialContent)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
data = []interface{}{}
|
||||
}
|
||||
|
||||
// Calculate data length after nil conversion
|
||||
dataLen := reflection.Len(data)
|
||||
|
||||
// Add X-No-Data-Found header when no records were found
|
||||
if dataLen == 0 {
|
||||
w.SetHeader("X-No-Data-Found", "true")
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// Normalize single-record arrays to objects if requested
|
||||
if options != nil && options.SingleRecordAsObject {
|
||||
data = h.normalizeResultArray(data)
|
||||
@@ -2165,7 +2175,7 @@ func (h *Handler) sendResponseWithOptions(w common.ResponseWriter, data interfac
|
||||
// Returns the single element if data is a slice/array with exactly one element, otherwise returns data unchanged
|
||||
func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
||||
if data == nil {
|
||||
return map[string]interface{}{}
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
// Use reflection to check if data is a slice or array
|
||||
@@ -2180,15 +2190,15 @@ func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
||||
// Return the single element
|
||||
return dataValue.Index(0).Interface()
|
||||
} else if dataValue.Len() == 0 {
|
||||
// Return empty object instead of empty array
|
||||
return map[string]interface{}{}
|
||||
// Keep empty array as empty array, don't convert to empty object
|
||||
return []interface{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if dataValue.Kind() == reflect.String {
|
||||
str := dataValue.String()
|
||||
if str == "" || str == "null" {
|
||||
return map[string]interface{}{}
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2199,16 +2209,25 @@ func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
||||
func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{}, metadata *common.Metadata, options ExtendedRequestOptions) {
|
||||
// Normalize single-record arrays to objects if requested
|
||||
httpStatus := http.StatusOK
|
||||
|
||||
// Handle nil data - convert to empty array
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
httpStatus = http.StatusPartialContent
|
||||
} else {
|
||||
dataLen := reflection.Len(data)
|
||||
if dataLen == 0 {
|
||||
httpStatus = http.StatusPartialContent
|
||||
}
|
||||
data = []interface{}{}
|
||||
}
|
||||
|
||||
// Calculate data length after nil conversion
|
||||
// Note: This is done BEFORE normalization because X-No-Data-Found indicates
|
||||
// whether data was found in the database, not the final response format
|
||||
dataLen := reflection.Len(data)
|
||||
|
||||
// Add X-No-Data-Found header when no records were found
|
||||
if dataLen == 0 {
|
||||
w.SetHeader("X-No-Data-Found", "true")
|
||||
}
|
||||
|
||||
// Apply normalization after header is set
|
||||
// normalizeResultArray may convert single-element arrays to objects,
|
||||
// but the X-No-Data-Found header reflects the original query result
|
||||
if options.SingleRecordAsObject {
|
||||
data = h.normalizeResultArray(data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user