mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-05-19 02:05:15 +00:00
fix(restheadspec): normalize empty results to objects instead of arrays
This commit is contained in:
@@ -9,29 +9,29 @@ import (
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common"
|
||||
)
|
||||
|
||||
// Test that normalizeResultArray returns empty array when no records found without ID
|
||||
// Test that normalizeResultArray returns empty object when no records found (single-record mode)
|
||||
func TestNormalizeResultArray_EmptyArrayWhenNoID(t *testing.T) {
|
||||
handler := &Handler{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
shouldBeEmptyArr bool
|
||||
name string
|
||||
input interface{}
|
||||
shouldBeEmptyObj bool
|
||||
}{
|
||||
{
|
||||
name: "nil should return empty array",
|
||||
input: nil,
|
||||
shouldBeEmptyArr: true,
|
||||
name: "nil should return empty object",
|
||||
input: nil,
|
||||
shouldBeEmptyObj: true,
|
||||
},
|
||||
{
|
||||
name: "empty slice should return empty array",
|
||||
input: []*EmptyTestModel{},
|
||||
shouldBeEmptyArr: true,
|
||||
name: "empty slice should return empty object",
|
||||
input: []*EmptyTestModel{},
|
||||
shouldBeEmptyObj: true,
|
||||
},
|
||||
{
|
||||
name: "single element should return the element",
|
||||
input: []*EmptyTestModel{{ID: 1, Name: "test"}},
|
||||
shouldBeEmptyArr: false,
|
||||
name: "single element should return the element",
|
||||
input: []*EmptyTestModel{{ID: 1, Name: "test"}},
|
||||
shouldBeEmptyObj: false,
|
||||
},
|
||||
{
|
||||
name: "multiple elements should return the slice",
|
||||
@@ -39,7 +39,7 @@ func TestNormalizeResultArray_EmptyArrayWhenNoID(t *testing.T) {
|
||||
{ID: 1, Name: "test1"},
|
||||
{ID: 2, Name: "test2"},
|
||||
},
|
||||
shouldBeEmptyArr: false,
|
||||
shouldBeEmptyObj: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,25 +47,25 @@ func TestNormalizeResultArray_EmptyArrayWhenNoID(t *testing.T) {
|
||||
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{})
|
||||
// For cases that should return empty object
|
||||
if tt.shouldBeEmptyObj {
|
||||
emptyObj, ok := result.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Errorf("Expected empty array []interface{}{}, got %T: %v", result, result)
|
||||
t.Errorf("Expected empty object map[string]interface{}{}, got %T: %v", result, result)
|
||||
return
|
||||
}
|
||||
if len(emptyArr) != 0 {
|
||||
t.Errorf("Expected empty array with length 0, got length %d", len(emptyArr))
|
||||
if len(emptyObj) != 0 {
|
||||
t.Errorf("Expected empty object with length 0, got length %d", len(emptyObj))
|
||||
}
|
||||
|
||||
// Verify it serializes to [] and not null
|
||||
// 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))
|
||||
if string(jsonBytes) != "{}" {
|
||||
t.Errorf("Expected JSON '{}', got '%s'", string(jsonBytes))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -138,12 +138,12 @@ func TestSendResponseWithOptions_NoDataFoundHeader(t *testing.T) {
|
||||
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)
|
||||
// Check status code is 204 when no records found
|
||||
if mockWriter.statusCode != 204 {
|
||||
t.Errorf("Expected status code 204, got %d", mockWriter.statusCode)
|
||||
}
|
||||
|
||||
// Verify the body is an empty array
|
||||
// Verify the body is an empty array (list request, SingleRecordAsObject not set)
|
||||
if mockWriter.body == nil {
|
||||
t.Error("Expected body to be set, got nil")
|
||||
} else {
|
||||
|
||||
@@ -2502,14 +2502,16 @@ func (h *Handler) sendResponseWithOptions(w common.ResponseWriter, data interfac
|
||||
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)
|
||||
}
|
||||
|
||||
// Return data as-is without wrapping in common.Response
|
||||
if dataLen == 0 {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
if err := w.WriteJSON(data); err != nil {
|
||||
logger.Error("Failed to write JSON response: %v", err)
|
||||
@@ -2520,7 +2522,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 []interface{}{}
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// Use reflection to check if data is a slice or array
|
||||
@@ -2535,15 +2537,15 @@ func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
||||
// Return the single element
|
||||
return dataValue.Index(0).Interface()
|
||||
} else if dataValue.Len() == 0 {
|
||||
// Keep empty array as empty array, don't convert to empty object
|
||||
return []interface{}{}
|
||||
// Single-record request with no result → empty object
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if dataValue.Kind() == reflect.String {
|
||||
str := dataValue.String()
|
||||
if str == "" || str == "null" {
|
||||
return []interface{}{}
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2552,9 +2554,6 @@ func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
||||
|
||||
// sendFormattedResponse sends response with formatting options
|
||||
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 = []interface{}{}
|
||||
@@ -2566,8 +2565,10 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
||||
dataLen := reflection.Len(data)
|
||||
|
||||
// Add X-No-Data-Found header when no records were found
|
||||
httpStatus := http.StatusOK
|
||||
if dataLen == 0 {
|
||||
w.SetHeader("X-No-Data-Found", "true")
|
||||
httpStatus = http.StatusNoContent
|
||||
}
|
||||
|
||||
// Apply normalization after header is set
|
||||
|
||||
Reference in New Issue
Block a user