From 66348dac974d0e056508646bda8c228fdf815380 Mon Sep 17 00:00:00 2001 From: Hein Date: Mon, 8 Jun 2026 09:06:29 +0200 Subject: [PATCH] test(handler): add tests for valid nested request verbs --- pkg/common/recursive_crud_test.go | 81 +++++++++++++++++++++++++ pkg/restheadspec/handler.go | 2 +- pkg/restheadspec/handler_nested_test.go | 39 ++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/pkg/common/recursive_crud_test.go b/pkg/common/recursive_crud_test.go index 67882f6..ce23b6c 100644 --- a/pkg/common/recursive_crud_test.go +++ b/pkg/common/recursive_crud_test.go @@ -846,6 +846,87 @@ func TestProcessNestedCUD_BelongsToUnchanged(t *testing.T) { } } +func TestProcessNestedCUD_AddAlias(t *testing.T) { + db := newMockDatabase() + registry := &mockModelRegistry{} + relProvider := newMockRelationshipProvider() + + processor := NewNestedCUDProcessor(db, registry, relProvider) + + data := map[string]interface{}{ + "_request": "add", + "name": "New Department", + } + + result, err := processor.ProcessNestedCUD(context.Background(), "insert", data, Department{}, nil, "departments") + if err != nil { + t.Fatalf("ProcessNestedCUD with _request=add failed: %v", err) + } + if result.ID == nil { + t.Error("Expected result.ID to be set after add") + } + if len(db.insertCalls) != 1 { + t.Errorf("Expected 1 insert call, got %d", len(db.insertCalls)) + } +} + +func TestProcessNestedCUD_RemoveAlias(t *testing.T) { + db := newMockDatabase() + registry := &mockModelRegistry{} + relProvider := newMockRelationshipProvider() + + processor := NewNestedCUDProcessor(db, registry, relProvider) + + data := map[string]interface{}{ + "_request": "remove", + "ID": int64(42), + } + + _, err := processor.ProcessNestedCUD(context.Background(), "delete", data, Department{}, nil, "departments") + if err != nil { + t.Fatalf("ProcessNestedCUD with _request=remove failed: %v", err) + } + if len(db.deleteCalls) != 1 { + t.Errorf("Expected 1 delete call, got %d", len(db.deleteCalls)) + } +} + +func TestProcessNestedCUD_NestedAddRemoveAliases(t *testing.T) { + db := newMockDatabase() + registry := &mockModelRegistry{} + relProvider := newMockRelationshipProvider() + + relProvider.RegisterRelation("Department", "employees", &RelationshipInfo{ + FieldName: "Employees", + JSONName: "employees", + RelationType: "has_many", + ForeignKey: "DepartmentID", + RelatedModel: Employee{}, + }) + + processor := NewNestedCUDProcessor(db, registry, relProvider) + + data := map[string]interface{}{ + "ID": int64(1), + "name": "Engineering", + "employees": []interface{}{ + map[string]interface{}{"_request": "add", "name": "Alice"}, + map[string]interface{}{"_request": "remove", "ID": int64(5)}, + }, + } + + _, err := processor.ProcessNestedCUD(context.Background(), "update", data, Department{}, nil, "departments") + if err != nil { + t.Fatalf("ProcessNestedCUD with nested add/remove failed: %v", err) + } + if len(db.insertCalls) != 1 { + t.Errorf("Expected 1 insert (add alias) for employee, got %d", len(db.insertCalls)) + } + if len(db.deleteCalls) != 1 { + t.Errorf("Expected 1 delete (remove alias) for employee, got %d", len(db.deleteCalls)) + } +} + func TestGetPrimaryKeyName(t *testing.T) { dept := Department{} pkName := reflection.GetPrimaryKeyName(dept) diff --git a/pkg/restheadspec/handler.go b/pkg/restheadspec/handler.go index bf99d67..d967f26 100644 --- a/pkg/restheadspec/handler.go +++ b/pkg/restheadspec/handler.go @@ -2120,7 +2120,7 @@ func isValidNestedRequest(item map[string]interface{}) bool { return false } switch strings.ToLower(strings.TrimSpace(s)) { - case "insert", "create", "add", "change", "update", "modify", "delete", "remove": + case "insert", "add", "change", "update", "delete", "remove": return true } return false diff --git a/pkg/restheadspec/handler_nested_test.go b/pkg/restheadspec/handler_nested_test.go index 605f01d..7bb2c64 100644 --- a/pkg/restheadspec/handler_nested_test.go +++ b/pkg/restheadspec/handler_nested_test.go @@ -352,6 +352,45 @@ func (m *mockRegistry) GetAllModels() map[string]interface{} { return m.models } +// TestIsValidNestedRequest verifies that only the allowed _request verbs are accepted +// and that items missing the key are rejected. +func TestIsValidNestedRequest(t *testing.T) { + tests := []struct { + name string + item map[string]interface{} + expected bool + }{ + // Valid verbs + {name: "insert", item: map[string]interface{}{"_request": "insert"}, expected: true}, + {name: "add", item: map[string]interface{}{"_request": "add"}, expected: true}, + {name: "update", item: map[string]interface{}{"_request": "update"}, expected: true}, + {name: "change", item: map[string]interface{}{"_request": "change"}, expected: true}, + {name: "delete", item: map[string]interface{}{"_request": "delete"}, expected: true}, + {name: "remove", item: map[string]interface{}{"_request": "remove"}, expected: true}, + // Case-insensitive + {name: "INSERT uppercase", item: map[string]interface{}{"_request": "INSERT"}, expected: true}, + {name: "Remove mixed case", item: map[string]interface{}{"_request": "Remove"}, expected: true}, + // Whitespace trimmed + {name: "insert with spaces", item: map[string]interface{}{"_request": " insert "}, expected: true}, + // Invalid / missing + {name: "missing _request", item: map[string]interface{}{"name": "foo"}, expected: false}, + {name: "empty string", item: map[string]interface{}{"_request": ""}, expected: false}, + {name: "unknown verb", item: map[string]interface{}{"_request": "create"}, expected: false}, + {name: "unknown verb modify", item: map[string]interface{}{"_request": "modify"}, expected: false}, + {name: "non-string value", item: map[string]interface{}{"_request": 42}, expected: false}, + {name: "nil value", item: map[string]interface{}{"_request": nil}, expected: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isValidNestedRequest(tt.item) + if got != tt.expected { + t.Errorf("isValidNestedRequest(%v) = %v, want %v", tt.item, got, tt.expected) + } + }) + } +} + // TestMultiLevelRelationExtraction tests extracting deeply nested relations func TestMultiLevelRelationExtraction(t *testing.T) { registry := &mockRegistry{