mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-15 15:34:25 +00:00
721 lines
20 KiB
Go
721 lines
20 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/reflection"
|
|
)
|
|
|
|
// Mock Database for testing
|
|
type mockDatabase struct {
|
|
insertCalls []map[string]interface{}
|
|
updateCalls []map[string]interface{}
|
|
deleteCalls []interface{}
|
|
lastID int64
|
|
}
|
|
|
|
func newMockDatabase() *mockDatabase {
|
|
return &mockDatabase{
|
|
insertCalls: make([]map[string]interface{}, 0),
|
|
updateCalls: make([]map[string]interface{}, 0),
|
|
deleteCalls: make([]interface{}, 0),
|
|
lastID: 1,
|
|
}
|
|
}
|
|
|
|
func (m *mockDatabase) NewSelect() SelectQuery { return &mockSelectQuery{} }
|
|
func (m *mockDatabase) NewInsert() InsertQuery { return &mockInsertQuery{db: m} }
|
|
func (m *mockDatabase) NewUpdate() UpdateQuery { return &mockUpdateQuery{db: m} }
|
|
func (m *mockDatabase) NewDelete() DeleteQuery { return &mockDeleteQuery{db: m} }
|
|
func (m *mockDatabase) RunInTransaction(ctx context.Context, fn func(Database) error) error {
|
|
return fn(m)
|
|
}
|
|
func (m *mockDatabase) Exec(ctx context.Context, query string, args ...interface{}) (Result, error) {
|
|
return &mockResult{rowsAffected: 1}, nil
|
|
}
|
|
func (m *mockDatabase) Query(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
|
return nil
|
|
}
|
|
func (m *mockDatabase) BeginTx(ctx context.Context) (Database, error) {
|
|
return m, nil
|
|
}
|
|
func (m *mockDatabase) CommitTx(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
func (m *mockDatabase) RollbackTx(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
func (m *mockDatabase) GetUnderlyingDB() interface{} {
|
|
return nil
|
|
}
|
|
|
|
// Mock SelectQuery
|
|
type mockSelectQuery struct{}
|
|
|
|
func (m *mockSelectQuery) Model(model interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Table(name string) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Column(columns ...string) SelectQuery { return m }
|
|
func (m *mockSelectQuery) ColumnExpr(query string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Where(condition string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) WhereOr(query string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Join(query string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) LeftJoin(query string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Preload(relation string, conditions ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) PreloadRelation(relation string, apply ...func(SelectQuery) SelectQuery) SelectQuery { return m }
|
|
func (m *mockSelectQuery) JoinRelation(relation string, apply ...func(SelectQuery) SelectQuery) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Order(order string) SelectQuery { return m }
|
|
func (m *mockSelectQuery) OrderExpr(order string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Limit(n int) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Offset(n int) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Group(group string) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Having(condition string, args ...interface{}) SelectQuery { return m }
|
|
func (m *mockSelectQuery) Scan(ctx context.Context, dest interface{}) error { return nil }
|
|
func (m *mockSelectQuery) ScanModel(ctx context.Context) error { return nil }
|
|
func (m *mockSelectQuery) Count(ctx context.Context) (int, error) { return 0, nil }
|
|
func (m *mockSelectQuery) Exists(ctx context.Context) (bool, error) { return false, nil }
|
|
|
|
// Mock InsertQuery
|
|
type mockInsertQuery struct {
|
|
db *mockDatabase
|
|
table string
|
|
values map[string]interface{}
|
|
}
|
|
|
|
func (m *mockInsertQuery) Model(model interface{}) InsertQuery { return m }
|
|
func (m *mockInsertQuery) Table(name string) InsertQuery {
|
|
m.table = name
|
|
return m
|
|
}
|
|
func (m *mockInsertQuery) Value(column string, value interface{}) InsertQuery {
|
|
if m.values == nil {
|
|
m.values = make(map[string]interface{})
|
|
}
|
|
m.values[column] = value
|
|
return m
|
|
}
|
|
func (m *mockInsertQuery) OnConflict(action string) InsertQuery { return m }
|
|
func (m *mockInsertQuery) Returning(columns ...string) InsertQuery { return m }
|
|
func (m *mockInsertQuery) Exec(ctx context.Context) (Result, error) {
|
|
// Record the insert call
|
|
m.db.insertCalls = append(m.db.insertCalls, m.values)
|
|
m.db.lastID++
|
|
return &mockResult{lastID: m.db.lastID, rowsAffected: 1}, nil
|
|
}
|
|
|
|
// Mock UpdateQuery
|
|
type mockUpdateQuery struct {
|
|
db *mockDatabase
|
|
table string
|
|
setValues map[string]interface{}
|
|
}
|
|
|
|
func (m *mockUpdateQuery) Model(model interface{}) UpdateQuery { return m }
|
|
func (m *mockUpdateQuery) Table(name string) UpdateQuery {
|
|
m.table = name
|
|
return m
|
|
}
|
|
func (m *mockUpdateQuery) Set(column string, value interface{}) UpdateQuery { return m }
|
|
func (m *mockUpdateQuery) SetMap(values map[string]interface{}) UpdateQuery {
|
|
m.setValues = values
|
|
return m
|
|
}
|
|
func (m *mockUpdateQuery) Where(condition string, args ...interface{}) UpdateQuery { return m }
|
|
func (m *mockUpdateQuery) Returning(columns ...string) UpdateQuery { return m }
|
|
func (m *mockUpdateQuery) Exec(ctx context.Context) (Result, error) {
|
|
// Record the update call
|
|
m.db.updateCalls = append(m.db.updateCalls, m.setValues)
|
|
return &mockResult{rowsAffected: 1}, nil
|
|
}
|
|
|
|
// Mock DeleteQuery
|
|
type mockDeleteQuery struct {
|
|
db *mockDatabase
|
|
table string
|
|
}
|
|
|
|
func (m *mockDeleteQuery) Model(model interface{}) DeleteQuery { return m }
|
|
func (m *mockDeleteQuery) Table(name string) DeleteQuery {
|
|
m.table = name
|
|
return m
|
|
}
|
|
func (m *mockDeleteQuery) Where(condition string, args ...interface{}) DeleteQuery { return m }
|
|
func (m *mockDeleteQuery) Exec(ctx context.Context) (Result, error) {
|
|
// Record the delete call
|
|
m.db.deleteCalls = append(m.db.deleteCalls, m.table)
|
|
return &mockResult{rowsAffected: 1}, nil
|
|
}
|
|
|
|
// Mock Result
|
|
type mockResult struct {
|
|
lastID int64
|
|
rowsAffected int64
|
|
}
|
|
|
|
func (m *mockResult) LastInsertId() (int64, error) { return m.lastID, nil }
|
|
func (m *mockResult) RowsAffected() int64 { return m.rowsAffected }
|
|
|
|
// Mock ModelRegistry
|
|
type mockModelRegistry struct{}
|
|
|
|
func (m *mockModelRegistry) GetModel(name string) (interface{}, error) { return nil, nil }
|
|
func (m *mockModelRegistry) GetModelByEntity(schema, entity string) (interface{}, error) { return nil, nil }
|
|
func (m *mockModelRegistry) RegisterModel(name string, model interface{}) error { return nil }
|
|
func (m *mockModelRegistry) GetAllModels() map[string]interface{} { return make(map[string]interface{}) }
|
|
|
|
// Mock RelationshipInfoProvider
|
|
type mockRelationshipProvider struct {
|
|
relationships map[string]*RelationshipInfo
|
|
}
|
|
|
|
func newMockRelationshipProvider() *mockRelationshipProvider {
|
|
return &mockRelationshipProvider{
|
|
relationships: make(map[string]*RelationshipInfo),
|
|
}
|
|
}
|
|
|
|
func (m *mockRelationshipProvider) GetRelationshipInfo(modelType reflect.Type, relationName string) *RelationshipInfo {
|
|
key := modelType.Name() + "." + relationName
|
|
return m.relationships[key]
|
|
}
|
|
|
|
func (m *mockRelationshipProvider) RegisterRelation(modelTypeName, relationName string, info *RelationshipInfo) {
|
|
key := modelTypeName + "." + relationName
|
|
m.relationships[key] = info
|
|
}
|
|
|
|
// Test Models
|
|
type Department struct {
|
|
ID int64 `json:"id" bun:"id,pk"`
|
|
Name string `json:"name"`
|
|
Employees []*Employee `json:"employees,omitempty"`
|
|
}
|
|
|
|
func (d Department) TableName() string { return "departments" }
|
|
func (d Department) GetIDName() string { return "ID" }
|
|
|
|
type Employee struct {
|
|
ID int64 `json:"id" bun:"id,pk"`
|
|
Name string `json:"name"`
|
|
DepartmentID int64 `json:"department_id"`
|
|
Tasks []*Task `json:"tasks,omitempty"`
|
|
}
|
|
|
|
func (e Employee) TableName() string { return "employees" }
|
|
func (e Employee) GetIDName() string { return "ID" }
|
|
|
|
type Task struct {
|
|
ID int64 `json:"id" bun:"id,pk"`
|
|
Title string `json:"title"`
|
|
EmployeeID int64 `json:"employee_id"`
|
|
Comments []*Comment `json:"comments,omitempty"`
|
|
}
|
|
|
|
func (t Task) TableName() string { return "tasks" }
|
|
func (t Task) GetIDName() string { return "ID" }
|
|
|
|
type Comment struct {
|
|
ID int64 `json:"id" bun:"id,pk"`
|
|
Text string `json:"text"`
|
|
TaskID int64 `json:"task_id"`
|
|
}
|
|
|
|
func (c Comment) TableName() string { return "comments" }
|
|
func (c Comment) GetIDName() string { return "ID" }
|
|
|
|
// Test Cases
|
|
|
|
func TestProcessNestedCUD_SingleLevelInsert(t *testing.T) {
|
|
db := newMockDatabase()
|
|
registry := &mockModelRegistry{}
|
|
relProvider := newMockRelationshipProvider()
|
|
|
|
// Register Department -> Employees relationship
|
|
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{}{
|
|
"name": "Engineering",
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "John Doe",
|
|
},
|
|
map[string]interface{}{
|
|
"name": "Jane Smith",
|
|
},
|
|
},
|
|
}
|
|
|
|
result, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"insert",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
if result.ID == nil {
|
|
t.Error("Expected result.ID to be set")
|
|
}
|
|
|
|
// Verify department was inserted
|
|
if len(db.insertCalls) != 3 {
|
|
t.Errorf("Expected 3 insert calls (1 dept + 2 employees), got %d", len(db.insertCalls))
|
|
}
|
|
|
|
// Verify first insert is department
|
|
if db.insertCalls[0]["name"] != "Engineering" {
|
|
t.Errorf("Expected department name 'Engineering', got %v", db.insertCalls[0]["name"])
|
|
}
|
|
|
|
// Verify employees were inserted with foreign key
|
|
if db.insertCalls[1]["department_id"] == nil {
|
|
t.Error("Expected employee to have department_id set")
|
|
}
|
|
if db.insertCalls[2]["department_id"] == nil {
|
|
t.Error("Expected employee to have department_id set")
|
|
}
|
|
}
|
|
|
|
func TestProcessNestedCUD_MultiLevelInsert(t *testing.T) {
|
|
db := newMockDatabase()
|
|
registry := &mockModelRegistry{}
|
|
relProvider := newMockRelationshipProvider()
|
|
|
|
// Register relationships
|
|
relProvider.RegisterRelation("Department", "employees", &RelationshipInfo{
|
|
FieldName: "Employees",
|
|
JSONName: "employees",
|
|
RelationType: "has_many",
|
|
ForeignKey: "DepartmentID",
|
|
RelatedModel: Employee{},
|
|
})
|
|
|
|
relProvider.RegisterRelation("Employee", "tasks", &RelationshipInfo{
|
|
FieldName: "Tasks",
|
|
JSONName: "tasks",
|
|
RelationType: "has_many",
|
|
ForeignKey: "EmployeeID",
|
|
RelatedModel: Task{},
|
|
})
|
|
|
|
processor := NewNestedCUDProcessor(db, registry, relProvider)
|
|
|
|
data := map[string]interface{}{
|
|
"name": "Engineering",
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "John Doe",
|
|
"tasks": []interface{}{
|
|
map[string]interface{}{
|
|
"title": "Task 1",
|
|
},
|
|
map[string]interface{}{
|
|
"title": "Task 2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"insert",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
if result.ID == nil {
|
|
t.Error("Expected result.ID to be set")
|
|
}
|
|
|
|
// Verify: 1 dept + 1 employee + 2 tasks = 4 inserts
|
|
if len(db.insertCalls) != 4 {
|
|
t.Errorf("Expected 4 insert calls, got %d", len(db.insertCalls))
|
|
}
|
|
|
|
// Verify department
|
|
if db.insertCalls[0]["name"] != "Engineering" {
|
|
t.Errorf("Expected department name 'Engineering', got %v", db.insertCalls[0]["name"])
|
|
}
|
|
|
|
// Verify employee has department_id
|
|
if db.insertCalls[1]["department_id"] == nil {
|
|
t.Error("Expected employee to have department_id set")
|
|
}
|
|
|
|
// Verify tasks have employee_id
|
|
if db.insertCalls[2]["employee_id"] == nil {
|
|
t.Error("Expected task to have employee_id set")
|
|
}
|
|
if db.insertCalls[3]["employee_id"] == nil {
|
|
t.Error("Expected task to have employee_id set")
|
|
}
|
|
}
|
|
|
|
func TestProcessNestedCUD_RequestFieldOverride(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{}{
|
|
"name": "Engineering",
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"_request": "update",
|
|
"ID": int64(10), // Use capital ID to match struct field
|
|
"name": "John Updated",
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"insert",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
// Verify department was inserted (1 insert)
|
|
// Employee should be updated (1 update)
|
|
if len(db.insertCalls) != 1 {
|
|
t.Errorf("Expected 1 insert call for department, got %d", len(db.insertCalls))
|
|
}
|
|
|
|
if len(db.updateCalls) != 1 {
|
|
t.Errorf("Expected 1 update call for employee, got %d", len(db.updateCalls))
|
|
}
|
|
|
|
// Verify update data
|
|
if db.updateCalls[0]["name"] != "John Updated" {
|
|
t.Errorf("Expected employee name 'John Updated', got %v", db.updateCalls[0]["name"])
|
|
}
|
|
}
|
|
|
|
func TestProcessNestedCUD_SkipInsertWhenOnlyRequestField(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 with only _request field for nested employee
|
|
data := map[string]interface{}{
|
|
"name": "Engineering",
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"_request": "insert",
|
|
// No other fields besides _request
|
|
// Note: Foreign key will be injected, so employee WILL be inserted
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"insert",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
// Department + Employee (with injected FK) = 2 inserts
|
|
if len(db.insertCalls) != 2 {
|
|
t.Errorf("Expected 2 insert calls (department + employee with FK), got %d", len(db.insertCalls))
|
|
}
|
|
|
|
if db.insertCalls[0]["name"] != "Engineering" {
|
|
t.Errorf("Expected department name 'Engineering', got %v", db.insertCalls[0]["name"])
|
|
}
|
|
|
|
// Verify employee has foreign key
|
|
if db.insertCalls[1]["department_id"] == nil {
|
|
t.Error("Expected employee to have department_id injected")
|
|
}
|
|
}
|
|
|
|
func TestProcessNestedCUD_Update(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), // Use capital ID to match struct field
|
|
"name": "Engineering Updated",
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"_request": "insert",
|
|
"name": "New Employee",
|
|
},
|
|
},
|
|
}
|
|
|
|
result, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"update",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
if result.ID != int64(1) {
|
|
t.Errorf("Expected result.ID to be 1, got %v", result.ID)
|
|
}
|
|
|
|
// Verify department was updated
|
|
if len(db.updateCalls) != 1 {
|
|
t.Errorf("Expected 1 update call, got %d", len(db.updateCalls))
|
|
}
|
|
|
|
// Verify new employee was inserted
|
|
if len(db.insertCalls) != 1 {
|
|
t.Errorf("Expected 1 insert call for new employee, got %d", len(db.insertCalls))
|
|
}
|
|
}
|
|
|
|
func TestProcessNestedCUD_Delete(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), // Use capital ID to match struct field
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"_request": "delete",
|
|
"ID": int64(10), // Use capital ID
|
|
},
|
|
map[string]interface{}{
|
|
"_request": "delete",
|
|
"ID": int64(11), // Use capital ID
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"delete",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
// Verify employees were deleted first, then department
|
|
// 2 employees + 1 department = 3 deletes
|
|
if len(db.deleteCalls) != 3 {
|
|
t.Errorf("Expected 3 delete calls, got %d", len(db.deleteCalls))
|
|
}
|
|
}
|
|
|
|
func TestProcessNestedCUD_ParentIDPropagation(t *testing.T) {
|
|
db := newMockDatabase()
|
|
registry := &mockModelRegistry{}
|
|
relProvider := newMockRelationshipProvider()
|
|
|
|
// Register 3-level relationships
|
|
relProvider.RegisterRelation("Department", "employees", &RelationshipInfo{
|
|
FieldName: "Employees",
|
|
JSONName: "employees",
|
|
RelationType: "has_many",
|
|
ForeignKey: "DepartmentID",
|
|
RelatedModel: Employee{},
|
|
})
|
|
|
|
relProvider.RegisterRelation("Employee", "tasks", &RelationshipInfo{
|
|
FieldName: "Tasks",
|
|
JSONName: "tasks",
|
|
RelationType: "has_many",
|
|
ForeignKey: "EmployeeID",
|
|
RelatedModel: Task{},
|
|
})
|
|
|
|
relProvider.RegisterRelation("Task", "comments", &RelationshipInfo{
|
|
FieldName: "Comments",
|
|
JSONName: "comments",
|
|
RelationType: "has_many",
|
|
ForeignKey: "TaskID",
|
|
RelatedModel: Comment{},
|
|
})
|
|
|
|
processor := NewNestedCUDProcessor(db, registry, relProvider)
|
|
|
|
data := map[string]interface{}{
|
|
"name": "Engineering",
|
|
"employees": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "John",
|
|
"tasks": []interface{}{
|
|
map[string]interface{}{
|
|
"title": "Task 1",
|
|
"comments": []interface{}{
|
|
map[string]interface{}{
|
|
"text": "Great work!",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := processor.ProcessNestedCUD(
|
|
context.Background(),
|
|
"insert",
|
|
data,
|
|
Department{},
|
|
nil,
|
|
"departments",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ProcessNestedCUD failed: %v", err)
|
|
}
|
|
|
|
// Verify: 1 dept + 1 employee + 1 task + 1 comment = 4 inserts
|
|
if len(db.insertCalls) != 4 {
|
|
t.Errorf("Expected 4 insert calls, got %d", len(db.insertCalls))
|
|
}
|
|
|
|
// Verify department
|
|
if db.insertCalls[0]["name"] != "Engineering" {
|
|
t.Error("Expected department to be inserted first")
|
|
}
|
|
|
|
// Verify employee has department_id
|
|
if db.insertCalls[1]["department_id"] == nil {
|
|
t.Error("Expected employee to have department_id")
|
|
}
|
|
|
|
// Verify task has employee_id
|
|
if db.insertCalls[2]["employee_id"] == nil {
|
|
t.Error("Expected task to have employee_id")
|
|
}
|
|
|
|
// Verify comment has task_id
|
|
if db.insertCalls[3]["task_id"] == nil {
|
|
t.Error("Expected comment to have task_id")
|
|
}
|
|
}
|
|
|
|
func TestInjectForeignKeys(t *testing.T) {
|
|
db := newMockDatabase()
|
|
registry := &mockModelRegistry{}
|
|
relProvider := newMockRelationshipProvider()
|
|
|
|
processor := NewNestedCUDProcessor(db, registry, relProvider)
|
|
|
|
data := map[string]interface{}{
|
|
"name": "John",
|
|
}
|
|
|
|
parentIDs := map[string]interface{}{
|
|
"department": int64(5),
|
|
}
|
|
|
|
modelType := reflect.TypeOf(Employee{})
|
|
|
|
processor.injectForeignKeys(data, modelType, parentIDs)
|
|
|
|
// Should inject department_id based on the "department" key in parentIDs
|
|
if data["department_id"] == nil {
|
|
t.Error("Expected department_id to be injected")
|
|
}
|
|
|
|
if data["department_id"] != int64(5) {
|
|
t.Errorf("Expected department_id to be 5, got %v", data["department_id"])
|
|
}
|
|
}
|
|
|
|
func TestGetPrimaryKeyName(t *testing.T) {
|
|
dept := Department{}
|
|
pkName := reflection.GetPrimaryKeyName(dept)
|
|
|
|
if pkName != "ID" {
|
|
t.Errorf("Expected primary key name 'ID', got '%s'", pkName)
|
|
}
|
|
|
|
// Test with pointer
|
|
pkName2 := reflection.GetPrimaryKeyName(&dept)
|
|
if pkName2 != "ID" {
|
|
t.Errorf("Expected primary key name 'ID' from pointer, got '%s'", pkName2)
|
|
}
|
|
}
|