From 54a3c1ce7c72d27076eee5ea085b50190a3be314 Mon Sep 17 00:00:00 2001 From: sehansi-9 Date: Mon, 30 Jun 2025 10:28:30 +0530 Subject: [PATCH 1/3] add attribute validator for tabular and graph structures --- nexoan/crud-api/pkg/schema/utils.go | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/nexoan/crud-api/pkg/schema/utils.go b/nexoan/crud-api/pkg/schema/utils.go index 743fe2e0..4ac37a68 100644 --- a/nexoan/crud-api/pkg/schema/utils.go +++ b/nexoan/crud-api/pkg/schema/utils.go @@ -387,3 +387,39 @@ func validateTabularValue(value interface{}, schema *SchemaInfo) error { return nil } + +// validateTabularAttributes checks that only 'columns' and 'rows' are present as keys in the map. +func validateTabularAttributes(obj map[string]interface{}) error { + required := []string{"columns", "rows"} + // Check required keys + for _, key := range required { + if _, exists := obj[key]; !exists { + return fmt.Errorf("tabular data must contain '%s' field", key) + } + } + // Check for extra keys + for key := range obj { + if key != "columns" && key != "rows" { + return fmt.Errorf("unexpected key '%s' in tabular data; only 'columns' and 'rows' are allowed", key) + } + } + return nil +} + +// validateGraphAttributes checks that only 'nodes' and 'edges' are present as keys in the map. +func validateGraphAttributes(obj map[string]interface{}) error { + required := []string{"nodes", "edges"} + // Check required keys + for _, key := range required { + if _, exists := obj[key]; !exists { + return fmt.Errorf("graph data must contain '%s' field", key) + } + } + // Check for extra keys + for key := range obj { + if key != "nodes" && key != "edges" { + return fmt.Errorf("unexpected key '%s' in graph data; only 'nodes' and 'edges' are allowed", key) + } + } + return nil +} From 1a55080900e8bca0923e97c914b858a841d6169d Mon Sep 17 00:00:00 2001 From: sehansi-9 Date: Mon, 30 Jun 2025 10:28:52 +0530 Subject: [PATCH 2/3] add test cases for attribute validation --- nexoan/crud-api/pkg/schema/utils_test.go | 295 +++++++++++++++++++++++ 1 file changed, 295 insertions(+) diff --git a/nexoan/crud-api/pkg/schema/utils_test.go b/nexoan/crud-api/pkg/schema/utils_test.go index f370bd34..ea50e287 100644 --- a/nexoan/crud-api/pkg/schema/utils_test.go +++ b/nexoan/crud-api/pkg/schema/utils_test.go @@ -582,3 +582,298 @@ func TestValidateTabularData(t *testing.T) { }) } } + +func TestValidateTabularAttributes(t *testing.T) { + testCases := []struct { + name string + input map[string]interface{} + expectError bool + errorMsg string + }{ + { + name: "valid tabular data with both required fields", + input: map[string]interface{}{ + "columns": []interface{}{"id", "name", "age"}, + "rows": []interface{}{ + []interface{}{1, "Alice", 30}, + []interface{}{2, "Bob", 25}, + }, + }, + expectError: false, + }, + { + name: "missing columns field", + input: map[string]interface{}{ + "rows": []interface{}{ + []interface{}{1, "Alice", 30}, + }, + }, + expectError: true, + errorMsg: "tabular data must contain 'columns' field", + }, + { + name: "missing rows field", + input: map[string]interface{}{ + "columns": []interface{}{"id", "name", "age"}, + }, + expectError: true, + errorMsg: "tabular data must contain 'rows' field", + }, + { + name: "missing both required fields", + input: map[string]interface{}{ + "data": "some data", + }, + expectError: true, + errorMsg: "tabular data must contain 'columns' field", + }, + { + name: "extra field present", + input: map[string]interface{}{ + "columns": []interface{}{"id", "name"}, + "rows": []interface{}{[]interface{}{1, "Alice"}}, + "extra": "should not be here", + }, + expectError: true, + errorMsg: "unexpected key 'extra' in tabular data; only 'columns' and 'rows' are allowed", + }, + { + name: "multiple extra fields present", + input: map[string]interface{}{ + "columns": []interface{}{"id", "name"}, + "rows": []interface{}{[]interface{}{1, "Alice"}}, + "extra1": "should not be here", + "extra2": "also should not be here", + }, + expectError: true, + errorMsg: "unexpected key 'extra", // Check for partial match since order is non-deterministic + }, + { + name: "empty object", + input: map[string]interface{}{}, + expectError: true, + errorMsg: "tabular data must contain 'columns' field", + }, + { + name: "only columns field", + input: map[string]interface{}{ + "columns": []interface{}{"id", "name"}, + }, + expectError: true, + errorMsg: "tabular data must contain 'rows' field", + }, + { + name: "only rows field", + input: map[string]interface{}{ + "rows": []interface{}{[]interface{}{1, "Alice"}}, + }, + expectError: true, + errorMsg: "tabular data must contain 'columns' field", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := validateTabularAttributes(tc.input) + if tc.expectError { + assert.Error(t, err) + if tc.errorMsg != "" { + assert.Contains(t, err.Error(), tc.errorMsg) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateGraphAttributes(t *testing.T) { + testCases := []struct { + name string + input map[string]interface{} + expectError bool + errorMsg string + }{ + { + name: "valid graph data with both required fields", + input: map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + "name": "Alice", + }, + }, + "edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + "type": "follows", + }, + }, + }, + expectError: false, + }, + { + name: "missing nodes field", + input: map[string]interface{}{ + "edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + "type": "follows", + }, + }, + }, + expectError: true, + errorMsg: "graph data must contain 'nodes' field", + }, + { + name: "missing edges field", + input: map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + "name": "Alice", + }, + }, + }, + expectError: true, + errorMsg: "graph data must contain 'edges' field", + }, + { + name: "missing both required fields", + input: map[string]interface{}{ + "data": "some data", + }, + expectError: true, + errorMsg: "graph data must contain 'nodes' field", + }, + { + name: "extra field present", + input: map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + }, + }, + "edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + }, + }, + "extra": "should not be here", + }, + expectError: true, + errorMsg: "unexpected key 'extra' in graph data; only 'nodes' and 'edges' are allowed", + }, + { + name: "multiple extra fields present", + input: map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + }, + }, + "edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + }, + }, + "extra1": "should not be here", + "extra2": "also should not be here", + }, + expectError: true, + errorMsg: "unexpected key 'extra", // Check for partial match since order is non-deterministic + }, + { + name: "empty object", + input: map[string]interface{}{}, + expectError: true, + errorMsg: "graph data must contain 'nodes' field", + }, + { + name: "only nodes field", + input: map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + }, + }, + }, + expectError: true, + errorMsg: "graph data must contain 'edges' field", + }, + { + name: "only edges field", + input: map[string]interface{}{ + "edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + }, + }, + }, + expectError: true, + errorMsg: "graph data must contain 'nodes' field", + }, + { + name: "case sensitive field names", + input: map[string]interface{}{ + "Nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + }, + }, + "Edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + }, + }, + }, + expectError: true, + errorMsg: "graph data must contain 'nodes' field", + }, + { + name: "mixed case field names", + input: map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": "1", + "type": "user", + }, + }, + "Edges": []interface{}{ + map[string]interface{}{ + "source": "1", + "target": "2", + }, + }, + }, + expectError: true, + errorMsg: "graph data must contain 'edges' field", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := validateGraphAttributes(tc.input) + if tc.expectError { + assert.Error(t, err) + if tc.errorMsg != "" { + assert.Contains(t, err.Error(), tc.errorMsg) + } + } else { + assert.NoError(t, err) + } + }) + } +} From fa3dc1dd844a609167d70141d55c16741a5b1db3 Mon Sep 17 00:00:00 2001 From: sehansi-9 Date: Mon, 14 Jul 2025 15:08:08 +0530 Subject: [PATCH 3/3] remove: remove redundant test cases from attribute validators for tabular and graph types --- nexoan/crud-api/pkg/schema/utils_test.go | 42 ------------------------ 1 file changed, 42 deletions(-) diff --git a/nexoan/crud-api/pkg/schema/utils_test.go b/nexoan/crud-api/pkg/schema/utils_test.go index ea50e287..d8bc3ea4 100644 --- a/nexoan/crud-api/pkg/schema/utils_test.go +++ b/nexoan/crud-api/pkg/schema/utils_test.go @@ -654,22 +654,6 @@ func TestValidateTabularAttributes(t *testing.T) { expectError: true, errorMsg: "tabular data must contain 'columns' field", }, - { - name: "only columns field", - input: map[string]interface{}{ - "columns": []interface{}{"id", "name"}, - }, - expectError: true, - errorMsg: "tabular data must contain 'rows' field", - }, - { - name: "only rows field", - input: map[string]interface{}{ - "rows": []interface{}{[]interface{}{1, "Alice"}}, - }, - expectError: true, - errorMsg: "tabular data must contain 'columns' field", - }, } for _, tc := range testCases { @@ -797,32 +781,6 @@ func TestValidateGraphAttributes(t *testing.T) { expectError: true, errorMsg: "graph data must contain 'nodes' field", }, - { - name: "only nodes field", - input: map[string]interface{}{ - "nodes": []interface{}{ - map[string]interface{}{ - "id": "1", - "type": "user", - }, - }, - }, - expectError: true, - errorMsg: "graph data must contain 'edges' field", - }, - { - name: "only edges field", - input: map[string]interface{}{ - "edges": []interface{}{ - map[string]interface{}{ - "source": "1", - "target": "2", - }, - }, - }, - expectError: true, - errorMsg: "graph data must contain 'nodes' field", - }, { name: "case sensitive field names", input: map[string]interface{}{