Files
archived-frankenphp/internal/extgen/validator_test.go
2025-12-15 12:50:50 +01:00

914 lines
21 KiB
Go

package extgen
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateFunction(t *testing.T) {
tests := []struct {
name string
function phpFunction
expectError bool
}{
{
name: "valid function",
function: phpFunction{
Name: "validFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "param1", PhpType: phpString},
{Name: "param2", PhpType: phpInt},
},
},
expectError: false,
},
{
name: "valid function with nullable return",
function: phpFunction{
Name: "nullableReturn",
ReturnType: phpString,
IsReturnNullable: true,
Params: []phpParameter{
{Name: "data", PhpType: phpArray},
},
},
expectError: false,
},
{
name: "valid function with array parameter",
function: phpFunction{
Name: "arrayFunction",
ReturnType: phpArray,
Params: []phpParameter{
{Name: "items", PhpType: phpArray},
{Name: "filter", PhpType: phpString},
},
},
expectError: false,
},
{
name: "valid function with nullable array parameter",
function: phpFunction{
Name: "nullableArrayFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "items", PhpType: phpArray, IsNullable: true},
{Name: "name", PhpType: phpString},
},
},
expectError: false,
},
{
name: "valid function with array parameter",
function: phpFunction{
Name: "arrayFunction",
ReturnType: "array",
Params: []phpParameter{
{Name: "items", PhpType: phpArray},
{Name: "filter", PhpType: phpString},
},
},
expectError: false,
},
{
name: "valid function with nullable array parameter",
function: phpFunction{
Name: "nullableArrayFunction",
ReturnType: "string",
Params: []phpParameter{
{Name: "items", PhpType: phpArray, IsNullable: true},
{Name: "name", PhpType: phpString},
},
},
expectError: false,
},
{
name: "valid function with callable parameter",
function: phpFunction{
Name: "callableFunction",
ReturnType: "array",
Params: []phpParameter{
{Name: "data", PhpType: phpArray},
{Name: "callback", PhpType: phpCallable},
},
},
expectError: false,
},
{
name: "valid function with nullable callable parameter",
function: phpFunction{
Name: "nullableCallableFunction",
ReturnType: "string",
Params: []phpParameter{
{Name: "callback", PhpType: phpCallable, IsNullable: true},
},
},
expectError: false,
},
{
name: "empty function name",
function: phpFunction{
Name: "",
ReturnType: phpString,
},
expectError: true,
},
{
name: "invalid function name - starts with number",
function: phpFunction{
Name: "123invalid",
ReturnType: phpString,
},
expectError: true,
},
{
name: "invalid function name - contains special chars",
function: phpFunction{
Name: "invalid-name",
ReturnType: phpString,
},
expectError: true,
},
{
name: "invalid parameter name",
function: phpFunction{
Name: "validName",
ReturnType: phpString,
Params: []phpParameter{
{Name: "123invalid", PhpType: phpString},
},
},
expectError: true,
},
{
name: "empty parameter name",
function: phpFunction{
Name: "validName",
ReturnType: phpString,
Params: []phpParameter{
{Name: "", PhpType: phpString},
},
},
expectError: true,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateFunction(tt.function)
if tt.expectError {
assert.Error(t, err, "validateFunction() should return an error for function %s", tt.function.Name)
} else {
assert.NoError(t, err, "validateFunction() should not return an error for function %s", tt.function.Name)
}
})
}
}
func TestValidateReturnType(t *testing.T) {
tests := []struct {
name string
returnType string
expectError bool
}{
{
name: "valid string type",
returnType: "string",
expectError: false,
},
{
name: "valid int type",
returnType: "int",
expectError: false,
},
{
name: "valid array type",
returnType: "array",
expectError: false,
},
{
name: "valid bool type",
returnType: "bool",
expectError: false,
},
{
name: "valid float type",
returnType: "float",
expectError: false,
},
{
name: "valid void type",
returnType: "void",
expectError: false,
},
{
name: "invalid return type",
returnType: "invalidType",
expectError: true,
},
{
name: "empty return type",
returnType: "",
expectError: true,
},
{
name: "case sensitive - String should be invalid",
returnType: "String",
expectError: true,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateReturnType(phpType(tt.returnType))
if tt.expectError {
assert.Error(t, err, "validateReturnType(%s) should return an error", tt.returnType)
} else {
assert.NoError(t, err, "validateReturnType(%s) should not return an error", tt.returnType)
}
})
}
}
func TestValidateClassProperty(t *testing.T) {
tests := []struct {
name string
prop phpClassProperty
expectError bool
}{
{
name: "valid property",
prop: phpClassProperty{
Name: "validProperty",
PhpType: phpString,
GoType: "string",
},
expectError: false,
},
{
name: "valid nullable property",
prop: phpClassProperty{
Name: "nullableProperty",
PhpType: phpInt,
GoType: "*int",
IsNullable: true,
},
expectError: false,
},
{
name: "empty property name",
prop: phpClassProperty{
Name: "",
PhpType: phpString,
},
expectError: true,
},
{
name: "invalid property name",
prop: phpClassProperty{
Name: "123invalid",
PhpType: phpString,
},
expectError: true,
},
{
name: "invalid property type",
prop: phpClassProperty{
Name: "validName",
PhpType: phpType("invalidType"),
},
expectError: true,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateClassProperty(tt.prop)
if tt.expectError {
assert.Error(t, err, "validateClassProperty() should return an error")
} else {
assert.NoError(t, err, "validateClassProperty() should not return an error")
}
})
}
}
func TestValidateParameter(t *testing.T) {
tests := []struct {
name string
param phpParameter
expectError bool
}{
{
name: "valid string parameter",
param: phpParameter{
Name: "validParam",
PhpType: phpString,
},
expectError: false,
},
{
name: "valid nullable parameter",
param: phpParameter{
Name: "nullableParam",
PhpType: phpInt,
IsNullable: true,
},
expectError: false,
},
{
name: "valid parameter with default",
param: phpParameter{
Name: "defaultParam",
PhpType: phpString,
HasDefault: true,
DefaultValue: "hello",
},
expectError: false,
},
{
name: "valid array parameter",
param: phpParameter{
Name: "arrayParam",
PhpType: phpArray,
},
expectError: false,
},
{
name: "valid nullable array parameter",
param: phpParameter{
Name: "nullableArrayParam",
PhpType: phpArray,
IsNullable: true,
},
expectError: false,
},
{
name: "valid callable parameter",
param: phpParameter{
Name: "callbackParam",
PhpType: phpCallable,
},
expectError: false,
},
{
name: "valid nullable callable parameter",
param: phpParameter{
Name: "nullableCallbackParam",
PhpType: "callable",
IsNullable: true,
},
expectError: false,
},
{
name: "empty parameter name",
param: phpParameter{
Name: "",
PhpType: phpString,
},
expectError: true,
},
{
name: "invalid parameter name",
param: phpParameter{
Name: "123invalid",
PhpType: phpString,
},
expectError: true,
},
{
name: "invalid parameter type",
param: phpParameter{
Name: "validName",
PhpType: phpType("invalidType"),
},
expectError: true,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateParameter(tt.param)
if tt.expectError {
assert.Error(t, err, "validateParameter() should return an error")
} else {
assert.NoError(t, err, "validateParameter() should not return an error")
}
})
}
}
func TestValidateClass(t *testing.T) {
tests := []struct {
name string
class phpClass
expectError bool
}{
{
name: "valid class",
class: phpClass{
Name: "ValidClass",
GoStruct: "ValidStruct",
Properties: []phpClassProperty{
{Name: "name", PhpType: phpString},
{Name: "age", PhpType: phpInt},
},
},
expectError: false,
},
{
name: "valid class with nullable properties",
class: phpClass{
Name: "NullableClass",
GoStruct: "NullableStruct",
Properties: []phpClassProperty{
{Name: "required", PhpType: phpString, IsNullable: false},
{Name: "optional", PhpType: phpString, IsNullable: true},
},
},
expectError: false,
},
{
name: "empty class name",
class: phpClass{
Name: "",
GoStruct: "ValidStruct",
},
expectError: true,
},
{
name: "invalid class name",
class: phpClass{
Name: "123InvalidClass",
GoStruct: "ValidStruct",
},
expectError: true,
},
{
name: "invalid property",
class: phpClass{
Name: "ValidClass",
GoStruct: "ValidStruct",
Properties: []phpClassProperty{
{Name: "123invalid", PhpType: phpString},
},
},
expectError: true,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateClass(tt.class)
if tt.expectError {
assert.Error(t, err, "validateClass() should return an error")
} else {
assert.NoError(t, err, "validateClass() should not return an error")
}
})
}
}
func TestValidateTypes(t *testing.T) {
tests := []struct {
name string
function phpFunction
expectError bool
errorMsg string
}{
{
name: "valid scalar parameters only",
function: phpFunction{
Name: "validFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "stringParam", PhpType: phpString},
{Name: "intParam", PhpType: phpInt},
{Name: "floatParam", PhpType: phpFloat},
{Name: "boolParam", PhpType: phpBool},
},
},
expectError: false,
},
{
name: "valid nullable scalar parameters",
function: phpFunction{
Name: "nullableFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "stringParam", PhpType: phpString, IsNullable: true},
{Name: "intParam", PhpType: phpInt, IsNullable: true},
},
},
expectError: false,
},
{
name: "valid void return type",
function: phpFunction{
Name: "voidFunction",
ReturnType: phpVoid,
Params: []phpParameter{
{Name: "stringParam", PhpType: phpString},
},
},
expectError: false,
},
{
name: "valid array parameter and return",
function: phpFunction{
Name: "arrayFunction",
ReturnType: phpArray,
Params: []phpParameter{
{Name: "arrayParam", PhpType: phpArray},
{Name: "stringParam", PhpType: phpString},
},
},
expectError: false,
},
{
name: "valid nullable array parameter",
function: phpFunction{
Name: "nullableArrayFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "arrayParam", PhpType: phpArray, IsNullable: true},
},
},
expectError: false,
},
{
name: "valid callable parameter",
function: phpFunction{
Name: "callableFunction",
ReturnType: "array",
Params: []phpParameter{
{Name: "callbackParam", PhpType: phpCallable},
},
},
expectError: false,
},
{
name: "valid nullable callable parameter",
function: phpFunction{
Name: "nullableCallableFunction",
ReturnType: "string",
Params: []phpParameter{
{Name: "callbackParam", PhpType: phpCallable, IsNullable: true},
},
},
expectError: false,
},
{
name: "invalid object parameter",
function: phpFunction{
Name: "objectFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "objectParam", PhpType: phpObject},
},
},
expectError: true,
errorMsg: `parameter 1 "objectParam" has unsupported type "object"`,
},
{
name: "invalid object return type",
function: phpFunction{
Name: "objectReturnFunction",
ReturnType: phpObject,
Params: []phpParameter{
{Name: "stringParam", PhpType: phpString},
},
},
expectError: true,
errorMsg: `return type "object" is not supported`,
},
{
name: "mixed scalar and invalid parameters",
function: phpFunction{
Name: "mixedFunction",
ReturnType: phpString,
Params: []phpParameter{
{Name: "validParam", PhpType: phpString},
{Name: "invalidParam", PhpType: phpObject},
{Name: "anotherValidParam", PhpType: phpInt},
},
},
expectError: true,
errorMsg: `parameter 2 "invalidParam" has unsupported type "object"`,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateTypes(tt.function)
if tt.expectError {
assert.Error(t, err, "validateTypes() should return an error for function %s", tt.function.Name)
assert.Contains(t, err.Error(), tt.errorMsg, "Error message should contain expected text")
} else {
assert.NoError(t, err, "validateTypes() should not return an error for function %s", tt.function.Name)
}
})
}
}
func TestValidateGoFunctionSignature(t *testing.T) {
tests := []struct {
name string
phpFunc phpFunction
expectError bool
errorMsg string
}{
{
name: "valid Go function signature",
phpFunc: phpFunction{
Name: "testFunc",
ReturnType: phpString,
Params: []phpParameter{
{Name: "name", PhpType: phpString},
{Name: "count", PhpType: phpInt},
},
GoFunction: `func testFunc(name *C.zend_string, count int64) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
{
name: "valid void return type",
phpFunc: phpFunction{
Name: "voidFunc",
ReturnType: phpVoid,
Params: []phpParameter{
{Name: "message", PhpType: phpString},
},
GoFunction: `func voidFunc(message *C.zend_string) {
// Do something
}`,
},
expectError: false,
},
{
name: "no Go function provided",
phpFunc: phpFunction{
Name: "noGoFunc",
ReturnType: phpString,
Params: []phpParameter{},
GoFunction: "",
},
expectError: true,
errorMsg: "no Go function found",
},
{
name: "parameter count mismatch",
phpFunc: phpFunction{
Name: "countMismatch",
ReturnType: phpString,
Params: []phpParameter{
{Name: "param1", PhpType: phpString},
{Name: "param2", PhpType: phpInt},
},
GoFunction: `func countMismatch(param1 *C.zend_string) unsafe.Pointer {
return nil
}`,
},
expectError: true,
errorMsg: "parameter count mismatch: PHP function has 2 parameters (expecting 2 Go parameters) but Go function has 1",
},
{
name: "parameter type mismatch",
phpFunc: phpFunction{
Name: "typeMismatch",
ReturnType: phpString,
Params: []phpParameter{
{Name: "name", PhpType: phpString},
{Name: "count", PhpType: phpInt},
},
GoFunction: `func typeMismatch(name *C.zend_string, count string) unsafe.Pointer {
return nil
}`,
},
expectError: true,
errorMsg: `parameter 2 type mismatch: PHP "int" requires Go type "int64" but found "string"`,
},
{
name: "return type mismatch",
phpFunc: phpFunction{
Name: "returnMismatch",
ReturnType: phpInt,
Params: []phpParameter{
{Name: "value", PhpType: phpString},
},
GoFunction: `func returnMismatch(value *C.zend_string) string {
return ""
}`,
},
expectError: true,
errorMsg: `return type mismatch: PHP "int" requires Go return type "int64" but found "string"`,
},
{
name: "valid bool parameter and return",
phpFunc: phpFunction{
Name: "boolFunc",
ReturnType: phpBool,
Params: []phpParameter{
{Name: "flag", PhpType: phpBool},
},
GoFunction: `func boolFunc(flag bool) bool {
return flag
}`,
},
expectError: false,
},
{
name: "valid float parameter and return",
phpFunc: phpFunction{
Name: "floatFunc",
ReturnType: phpFloat,
Params: []phpParameter{
{Name: "value", PhpType: phpFloat},
},
GoFunction: `func floatFunc(value float64) float64 {
return value * 2.0
}`,
},
expectError: false,
},
{
name: "valid array parameter and return",
phpFunc: phpFunction{
Name: "arrayFunc",
ReturnType: phpArray,
Params: []phpParameter{
{Name: "items", PhpType: phpArray},
},
GoFunction: `func arrayFunc(items *C.zend_array) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
{
name: "valid nullable array parameter",
phpFunc: phpFunction{
Name: "nullableArrayFunc",
ReturnType: phpString,
Params: []phpParameter{
{Name: "items", PhpType: phpArray, IsNullable: true},
{Name: "name", PhpType: phpString},
},
GoFunction: `func nullableArrayFunc(items *C.zend_array, name *C.zend_string) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
{
name: "mixed array and scalar parameters",
phpFunc: phpFunction{
Name: "mixedFunc",
ReturnType: phpArray,
Params: []phpParameter{
{Name: "data", PhpType: phpArray},
{Name: "filter", PhpType: phpString},
{Name: "limit", PhpType: phpInt},
},
GoFunction: `func mixedFunc(data *C.zend_array, filter *C.zend_string, limit int64) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
{
name: "valid callable parameter",
phpFunc: phpFunction{
Name: "callableFunc",
ReturnType: "array",
Params: []phpParameter{
{Name: "callback", PhpType: phpCallable},
},
GoFunction: `func callableFunc(callback *C.zval) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
{
name: "valid nullable callable parameter",
phpFunc: phpFunction{
Name: "nullableCallableFunc",
ReturnType: "string",
Params: []phpParameter{
{Name: "callback", PhpType: phpCallable, IsNullable: true},
},
GoFunction: `func nullableCallableFunc(callback *C.zval) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
{
name: "mixed callable and other parameters",
phpFunc: phpFunction{
Name: "mixedCallableFunc",
ReturnType: "array",
Params: []phpParameter{
{Name: "data", PhpType: phpArray},
{Name: "callback", PhpType: phpCallable},
{Name: "options", PhpType: "int"},
},
GoFunction: `func mixedCallableFunc(data *C.zend_array, callback *C.zval, options int64) unsafe.Pointer {
return nil
}`,
},
expectError: false,
},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validator.validateGoFunctionSignatureWithOptions(tt.phpFunc, false)
if tt.expectError {
assert.Error(t, err, "validateGoFunctionSignature() should return an error for function %s", tt.phpFunc.Name)
assert.Contains(t, err.Error(), tt.errorMsg, "Error message should contain expected text")
} else {
assert.NoError(t, err, "validateGoFunctionSignature() should not return an error for function %s", tt.phpFunc.Name)
}
})
}
}
func TestPhpTypeToGoType(t *testing.T) {
tests := []struct {
phpType string
isNullable bool
expected string
}{
{"string", false, "*C.zend_string"},
{"string", true, "*C.zend_string"},
{"int", false, "int64"},
{"int", true, "*int64"},
{"float", false, "float64"},
{"float", true, "*float64"},
{"bool", false, "bool"},
{"bool", true, "*bool"},
{"array", false, "*C.zend_array"},
{"array", true, "*C.zend_array"},
{"callable", false, "*C.zval"},
{"callable", true, "*C.zval"},
{"unknown", false, "any"},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.phpType, func(t *testing.T) {
result := validator.phpTypeToGoType(phpType(tt.phpType), tt.isNullable)
assert.Equal(t, tt.expected, result, "phpTypeToGoType(%s, %v) should return %s", tt.phpType, tt.isNullable, tt.expected)
})
}
}
func TestPhpReturnTypeToGoType(t *testing.T) {
tests := []struct {
phpReturnType string
expected string
}{
{"void", ""},
{"void", ""},
{"string", "unsafe.Pointer"},
{"string", "unsafe.Pointer"},
{"int", "int64"},
{"int", "int64"},
{"float", "float64"},
{"float", "float64"},
{"bool", "bool"},
{"bool", "bool"},
{"array", "unsafe.Pointer"},
{"array", "unsafe.Pointer"},
{"unknown", "any"},
}
validator := Validator{}
for _, tt := range tests {
t.Run(tt.phpReturnType, func(t *testing.T) {
result := validator.phpReturnTypeToGoType(phpType(tt.phpReturnType))
assert.Equal(t, tt.expected, result, "phpReturnTypeToGoType(%s) should return %s", tt.phpReturnType, tt.expected)
})
}
}