repogen/internal/spec/parser_test.go
2023-05-24 14:04:29 +03:00

2514 lines
68 KiB
Go

package spec_test
import (
"errors"
"reflect"
"testing"
"git.kmsign.ru/royalcat/repogen/internal/code"
"git.kmsign.ru/royalcat/repogen/internal/spec"
)
var (
idField = code.StructField{
Name: "ID",
Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"},
}
phoneNumberField = code.StructField{
Name: "PhoneNumber",
Type: code.TypeString,
}
genderField = code.StructField{
Name: "Gender",
Type: code.SimpleType("Gender"),
}
cityField = code.StructField{
Name: "City",
Type: code.TypeString,
}
ageField = code.StructField{
Name: "Age",
Type: code.TypeInt,
}
nameField = code.StructField{
Name: "Name",
Type: code.SimpleType("NameModel"),
}
contactField = code.StructField{
Name: "Contact",
Type: code.SimpleType("ContactModel"),
}
referrerField = code.StructField{
Name: "Referrer",
Type: code.PointerType{ContainedType: code.SimpleType("UserModel")},
}
defaultPaymentField = code.StructField{
Name: "DefaultPayment",
Type: code.ExternalType{PackageAlias: "payment", Name: "Payment"},
}
enabledField = code.StructField{
Name: "Enabled",
Type: code.TypeBool,
}
consentHistoryField = code.StructField{
Name: "ConsentHistory",
Type: code.ArrayType{ContainedType: code.SimpleType("ConsentHistoryItem")},
}
firstNameField = code.StructField{
Name: "First",
Type: code.TypeString,
}
lastNameField = code.StructField{
Name: "Last",
Type: code.TypeString,
}
)
var (
nameStruct = code.Struct{
Name: "NameModel",
Fields: code.StructFields{
firstNameField,
lastNameField,
},
}
structModel = code.Struct{
Name: "UserModel",
Fields: code.StructFields{
idField,
phoneNumberField,
genderField,
cityField,
ageField,
nameField,
contactField,
referrerField,
defaultPaymentField,
consentHistoryField,
enabledField,
},
}
)
var structs = map[string]code.Struct{
nameStruct.Name: nameStruct,
structModel.Name: structModel,
}
type ParseInterfaceMethodTestCase struct {
Name string
Method code.Method
ExpectedOperation spec.Operation
}
func TestParseInterfaceMethod_Insert(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "InsertOne method",
Method: code.Method{
Name: "InsertOne",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
},
Returns: []code.Type{
code.InterfaceType{},
code.TypeError,
},
},
ExpectedOperation: spec.InsertOperation{
Mode: spec.QueryModeOne,
},
},
{
Name: "InsertMany method",
Method: code.Method{
Name: "InsertMany",
Params: []code.Param{
{
Type: code.ExternalType{
PackageAlias: "context",
Name: "Context",
},
},
{
Type: code.ArrayType{
ContainedType: code.PointerType{
ContainedType: code.SimpleType("UserModel"),
},
},
},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.InterfaceType{}},
code.TypeError,
},
},
ExpectedOperation: spec.InsertOperation{
Mode: spec.QueryModeMany,
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
}
expectedOutput := spec.MethodSpec{
Name: testCase.Method.Name,
Params: testCase.Method.Params,
Returns: testCase.Method.Returns,
Operation: testCase.ExpectedOperation,
}
if !reflect.DeepEqual(actualSpec, expectedOutput) {
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
}
})
}
}
func TestParseInterfaceMethod_Find(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "FindByArg one-mode method",
Method: code.Method{
Name: "FindByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
{
Name: "FindByArg many-mode method",
Method: code.Method{
Name: "FindByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
{
Name: "FindByMultiWordArg method",
Method: code.Method{
Name: "FindByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{phoneNumberField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByDeepArg method",
Method: code.Method{
Name: "FindByNameFirst",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{nameField, firstNameField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByDeepPointerArg method",
Method: code.Method{
Name: "FindByReferrerID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{referrerField, idField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindAll method",
Method: code.Method{
Name: "FindAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
},
},
{
Name: "FindByArgAndArg method",
Method: code.Method{
Name: "FindByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{cityField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
{
FieldReference: spec.FieldReference{genderField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
},
},
},
},
{
Name: "FindByArgOrArg method",
Method: code.Method{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{cityField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
{
FieldReference: spec.FieldReference{genderField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
},
},
},
},
{
Name: "FindByArgNot method",
Method: code.Method{
Name: "FindByCityNot",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNot, ParamIndex: 1},
}},
},
},
{
Name: "FindByArgLessThan method",
Method: code.Method{
Name: "FindByAgeLessThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1},
}},
},
},
{
Name: "FindByArgLessThanEqual method",
Method: code.Method{
Name: "FindByAgeLessThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{ageField},
Comparator: spec.ComparatorLessThanEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByArgGreaterThan method",
Method: code.Method{
Name: "FindByAgeGreaterThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{ageField},
Comparator: spec.ComparatorGreaterThan,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByArgGreaterThanEqual method",
Method: code.Method{
Name: "FindByAgeGreaterThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{ageField},
Comparator: spec.ComparatorGreaterThanEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByArgBetween method",
Method: code.Method{
Name: "FindByAgeBetween",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1},
}},
},
},
{
Name: "FindByArgIn method",
Method: code.Method{
Name: "FindByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ArrayType{ContainedType: code.TypeString}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorIn, ParamIndex: 1},
}},
},
},
{
Name: "FindByArgNotIn method",
Method: code.Method{
Name: "FindByCityNotIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ArrayType{ContainedType: code.TypeString}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNotIn, ParamIndex: 1},
}},
},
},
{
Name: "FindByArgTrue method",
Method: code.Method{
Name: "FindByEnabledTrue",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{enabledField}, Comparator: spec.ComparatorTrue, ParamIndex: 1},
}},
},
},
{
Name: "FindByArgFalse method",
Method: code.Method{
Name: "FindByEnabledFalse",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{enabledField},
Comparator: spec.ComparatorFalse,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByArgExists method",
Method: code.Method{
Name: "FindByReferrerExists",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{referrerField},
Comparator: spec.ComparatorExists,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByArgNotExists method",
Method: code.Method{
Name: "FindByReferrerNotExists",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{referrerField},
Comparator: spec.ComparatorNotExists,
ParamIndex: 1,
},
}},
},
},
{
Name: "FindByArgOrderByArg method",
Method: code.Method{
Name: "FindByCityOrderByAge",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending},
},
},
},
{
Name: "FindByArgOrderByArgAsc method",
Method: code.Method{
Name: "FindByCityOrderByAgeAsc",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending},
},
},
},
{
Name: "FindByArgOrderByArgDesc method",
Method: code.Method{
Name: "FindByCityOrderByAgeDesc",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
},
},
},
{
Name: "FindByArgOrderByDeepArg method",
Method: code.Method{
Name: "FindByCityOrderByNameFirst",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{nameField, firstNameField}, Ordering: spec.OrderingAscending},
},
},
},
{
Name: "FindByArgOrderByArgAndArg method",
Method: code.Method{
Name: "FindByCityOrderByCityAndAgeDesc",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{cityField}, Ordering: spec.OrderingAscending},
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
},
},
},
{
Name: "FindTopNByArg method",
Method: code.Method{
Name: "FindTop5ByGenderOrderByAgeDesc",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
},
Limit: 5,
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
}
expectedOutput := spec.MethodSpec{
Name: testCase.Method.Name,
Params: testCase.Method.Params,
Returns: testCase.Method.Returns,
Operation: testCase.ExpectedOperation,
}
if !reflect.DeepEqual(actualSpec, expectedOutput) {
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
}
})
}
}
func TestParseInterfaceMethod_Update(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "UpdateByArg",
Method: code.Method{
Name: "UpdateByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateModel{},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
}},
},
},
{
Name: "UpdateArgByArg one method",
Method: code.Method{
Name: "UpdateGenderByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{genderField},
ParamIndex: 1,
Operator: spec.UpdateOperatorSet,
},
},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{idField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
}},
},
},
{
Name: "UpdateArgByArg many method",
Method: code.Method{
Name: "UpdateGenderByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{genderField},
ParamIndex: 1,
Operator: spec.UpdateOperatorSet,
},
},
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{idField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
}},
},
},
{
Name: "UpdateArgByArg one with deeply referenced update field method",
Method: code.Method{
Name: "UpdateNameFirstByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{nameField, firstNameField},
ParamIndex: 1,
Operator: spec.UpdateOperatorSet,
},
},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{idField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
}},
},
},
{
Name: "UpdateArgAndArgByArg method",
Method: code.Method{
Name: "UpdateGenderAndCityByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
{Type: code.TypeString},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{genderField},
ParamIndex: 1,
Operator: spec.UpdateOperatorSet,
},
spec.UpdateField{
FieldReference: spec.FieldReference{cityField},
ParamIndex: 2,
Operator: spec.UpdateOperatorSet,
},
},
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 3},
}},
},
},
{
Name: "UpdateArgPushByArg method",
Method: code.Method{
Name: "UpdateConsentHistoryPushByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("ConsentHistoryItem")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{consentHistoryField},
ParamIndex: 1,
Operator: spec.UpdateOperatorPush,
},
},
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{idField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
}},
},
},
{
Name: "UpdateArgPushByArg method",
Method: code.Method{
Name: "UpdateAgeIncByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{ageField},
ParamIndex: 1,
Operator: spec.UpdateOperatorInc,
},
},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{idField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
}},
},
},
{
Name: "UpdateArgAndArgPushByArg method",
Method: code.Method{
Name: "UpdateEnabledAndConsentHistoryPushByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeBool},
{Type: code.SimpleType("ConsentHistoryItem")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
spec.UpdateField{
FieldReference: spec.FieldReference{enabledField},
ParamIndex: 1,
Operator: spec.UpdateOperatorSet,
},
spec.UpdateField{
FieldReference: spec.FieldReference{consentHistoryField},
ParamIndex: 2,
Operator: spec.UpdateOperatorPush,
},
},
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 3},
}},
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
}
expectedOutput := spec.MethodSpec{
Name: testCase.Method.Name,
Params: testCase.Method.Params,
Returns: testCase.Method.Returns,
Operation: testCase.ExpectedOperation,
}
if !reflect.DeepEqual(actualSpec, expectedOutput) {
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
}
})
}
}
func TestParseInterfaceMethod_Delete(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "DeleteByArg one-mode method",
Method: code.Method{
Name: "DeleteByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
{
Name: "DeleteByArg many-mode method",
Method: code.Method{
Name: "DeleteByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
{
Name: "DeleteByMultiWordArg method",
Method: code.Method{
Name: "DeleteByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{phoneNumberField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "DeleteAll method",
Method: code.Method{
Name: "DeleteAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
},
},
{
Name: "DeleteByArgAndArg method",
Method: code.Method{
Name: "DeleteByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{cityField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
{
FieldReference: spec.FieldReference{genderField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
},
},
},
},
{
Name: "DeleteByArgOrArg method",
Method: code.Method{
Name: "DeleteByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{cityField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
{
FieldReference: spec.FieldReference{genderField},
Comparator: spec.ComparatorEqual,
ParamIndex: 2,
},
},
},
},
},
{
Name: "DeleteByArgNot method",
Method: code.Method{
Name: "DeleteByCityNot",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNot, ParamIndex: 1},
}},
},
},
{
Name: "DeleteByArgLessThan method",
Method: code.Method{
Name: "DeleteByAgeLessThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1},
}},
},
},
{
Name: "DeleteByArgLessThanEqual method",
Method: code.Method{
Name: "DeleteByAgeLessThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{ageField},
Comparator: spec.ComparatorLessThanEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "DeleteByArgGreaterThan method",
Method: code.Method{
Name: "DeleteByAgeGreaterThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{ageField},
Comparator: spec.ComparatorGreaterThan,
ParamIndex: 1,
},
}},
},
},
{
Name: "DeleteByArgGreaterThanEqual method",
Method: code.Method{
Name: "DeleteByAgeGreaterThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{ageField},
Comparator: spec.ComparatorGreaterThanEqual,
ParamIndex: 1,
},
}},
},
},
{
Name: "DeleteByArgBetween method",
Method: code.Method{
Name: "DeleteByAgeBetween",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1},
}},
},
},
{
Name: "DeleteByArgIn method",
Method: code.Method{
Name: "DeleteByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ArrayType{ContainedType: code.TypeString}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorIn, ParamIndex: 1},
}},
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
}
expectedOutput := spec.MethodSpec{
Name: testCase.Method.Name,
Params: testCase.Method.Params,
Returns: testCase.Method.Returns,
Operation: testCase.ExpectedOperation,
}
if !reflect.DeepEqual(actualSpec, expectedOutput) {
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
}
})
}
}
func TestParseInterfaceMethod_Count(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "CountAll method",
Method: code.Method{
Name: "CountAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.CountOperation{
Query: spec.QuerySpec{},
},
},
{
Name: "CountByArg method",
Method: code.Method{
Name: "CountByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedOperation: spec.CountOperation{
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{
FieldReference: spec.FieldReference{genderField},
Comparator: spec.ComparatorEqual,
ParamIndex: 1,
},
},
},
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
}
expectedOutput := spec.MethodSpec{
Name: testCase.Method.Name,
Params: testCase.Method.Params,
Returns: testCase.Method.Returns,
Operation: testCase.ExpectedOperation,
}
if !reflect.DeepEqual(actualSpec, expectedOutput) {
t.Errorf("Expected = %+v\nReceived = %+v", expectedOutput, actualSpec)
}
})
}
}
type ParseInterfaceMethodInvalidTestCase struct {
Name string
Method code.Method
ExpectedError error
}
func TestParseInterfaceMethod_Invalid(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structs, structModel, code.Method{
Name: "SearchByID",
})
expectedError := spec.NewUnknownOperationError("Search")
if !errors.Is(err, expectedError) {
t.Errorf("\nExpected = %+v\nReceived = %+v", expectedError, err)
}
}
func TestParseInterfaceMethod_Insert_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "invalid number of returns",
Method: code.Method{
Name: "Insert",
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.InterfaceType{},
code.TypeError,
},
},
ExpectedError: spec.NewOperationReturnCountUnmatchedError(2),
},
{
Name: "unsupported return types from insert method",
Method: code.Method{
Name: "Insert",
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.TypeError,
},
},
ExpectedError: spec.NewUnsupportedReturnError(
code.PointerType{ContainedType: code.SimpleType("UserModel")},
0,
),
},
{
Name: "unempty interface return from insert method",
Method: code.Method{
Name: "Insert",
Returns: []code.Type{
code.InterfaceType{
Methods: []code.Method{
{Name: "DoSomething"},
},
},
code.TypeError,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.InterfaceType{}, 0),
},
{
Name: "error return not provided",
Method: code.Method{
Name: "Insert",
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.InterfaceType{},
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.InterfaceType{}, 1),
},
{
Name: "no context parameter",
Method: code.Method{
Name: "Insert",
Params: []code.Param{
{Name: "userModel", Type: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
},
Returns: []code.Type{
code.InterfaceType{},
code.TypeError,
},
},
ExpectedError: spec.ErrContextParamRequired,
},
{
Name: "mismatched model parameter for one mode",
Method: code.Method{
Name: "Insert",
Params: []code.Param{
{
Name: "ctx",
Type: code.ExternalType{PackageAlias: "context", Name: "Context"},
},
{
Name: "userModel",
Type: code.ArrayType{
ContainedType: code.PointerType{
ContainedType: code.SimpleType("UserModel"),
},
},
},
},
Returns: []code.Type{
code.InterfaceType{},
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidParam,
},
{
Name: "mismatched model parameter for many mode",
Method: code.Method{
Name: "Insert",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "userModel", Type: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.InterfaceType{}},
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidParam,
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err.Error() != testCase.ExpectedError.Error() {
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
}
})
}
}
func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "invalid number of returns",
Method: code.Method{
Name: "FindByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewOperationReturnCountUnmatchedError(2),
},
{
Name: "unsupported return types from find method",
Method: code.Method{
Name: "FindByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.TypeError,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.SimpleType("UserModel"), 0),
},
{
Name: "error return not provided",
Method: code.Method{
Name: "FindByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.TypeInt,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.TypeInt, 1),
},
{
Name: "find method with Top keyword but no number and query",
Method: code.Method{
Name: "FindTop",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.ErrLimitAmountRequired,
},
{
Name: "find method with Top keyword but no number",
Method: code.Method{
Name: "FindTopAll",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.ErrLimitAmountRequired,
},
{
Name: "find method with TopN keyword where N is not positive",
Method: code.Method{
Name: "FindTop0All",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.ErrLimitNonPositive,
},
{
Name: "find one method with TopN keyword",
Method: code.Method{
Name: "FindTop5All",
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.TypeError,
},
},
ExpectedError: spec.ErrLimitOnFindOne,
},
{
Name: "find method without query",
Method: code.Method{
Name: "Find",
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.TypeError,
},
},
ExpectedError: spec.ErrQueryRequired,
},
{
Name: "misplaced operator token (leftmost)",
Method: code.Method{
Name: "FindByAndGender",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"And", "Gender"}),
},
{
Name: "misplaced operator token (rightmost)",
Method: code.Method{
Name: "FindByGenderAnd",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And"}),
},
{
Name: "misplaced operator token (double operator)",
Method: code.Method{
Name: "FindByGenderAndAndCity",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "And", "City"}),
},
{
Name: "ambiguous query",
Method: code.Method{
Name: "FindByGenderAndCityOrAge",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "City", "Or", "Age"}),
},
{
Name: "no context parameter",
Method: code.Method{
Name: "FindByGender",
Params: []code.Param{
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.ErrContextParamRequired,
},
{
Name: "mismatched number of parameters",
Method: code.Method{
Name: "FindByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidParam,
},
{
Name: "struct field not found",
Method: code.Method{
Name: "FindByCountry",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
{
Name: "deeply referenced struct field not found",
Method: code.Method{
Name: "FindByNameMiddle",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Name", "Middle"}),
},
{
Name: "deeply referenced struct not found",
Method: code.Method{
Name: "FindByContactPhone",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Contact", "Phone"}),
},
{
Name: "deeply referenced external struct field",
Method: code.Method{
Name: "FindByDefaultPaymentMethod",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Default", "Payment", "Method"}),
},
{
Name: "incompatible struct field for True comparator",
Method: code.Method{
Name: "FindByGenderTrue",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewIncompatibleComparatorError(spec.ComparatorTrue, code.StructField{
Name: "Gender",
Type: code.SimpleType("Gender"),
}),
},
{
Name: "incompatible struct field for False comparator",
Method: code.Method{
Name: "FindByGenderFalse",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewIncompatibleComparatorError(spec.ComparatorFalse, code.StructField{
Name: "Gender",
Type: code.SimpleType("Gender"),
}),
},
{
Name: "mismatched method parameter type",
Method: code.Method{
Name: "FindByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError(genderField.Name, genderField.Type, code.TypeString),
},
{
Name: "mismatched method parameter type for special case",
Method: code.Method{
Name: "FindByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError(cityField.Name,
code.ArrayType{ContainedType: code.TypeString}, code.TypeString),
},
{
Name: "misplaced operator token (leftmost)",
Method: code.Method{
Name: "FindAllOrderByAndAge",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidSortError([]string{"Order", "By", "And", "Age"}),
},
{
Name: "misplaced operator token (rightmost)",
Method: code.Method{
Name: "FindAllOrderByAgeAnd",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidSortError([]string{"Order", "By", "Age", "And"}),
},
{
Name: "misplaced operator token (double operator)",
Method: code.Method{
Name: "FindAllOrderByAgeAndAndGender",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewInvalidSortError([]string{"Order", "By", "Age", "And", "And", "Gender"}),
},
{
Name: "sort field not found",
Method: code.Method{
Name: "FindAllOrderByCountry",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err.Error() != testCase.ExpectedError.Error() {
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError.Error(), err.Error())
}
})
}
}
func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "invalid number of returns",
Method: code.Method{
Name: "UpdateAgeByID",
Returns: []code.Type{
code.TypeBool,
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewOperationReturnCountUnmatchedError(2),
},
{
Name: "unsupported return types from update method",
Method: code.Method{
Name: "UpdateAgeByID",
Returns: []code.Type{
code.TypeFloat64,
code.TypeError,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.TypeFloat64, 0),
},
{
Name: "error return not provided",
Method: code.Method{
Name: "UpdateAgeByID",
Returns: []code.Type{
code.TypeBool,
code.TypeBool,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.TypeBool, 1),
},
{
Name: "update with no field provided",
Method: code.Method{
Name: "UpdateByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidUpdateFields,
},
{
Name: "misplaced And token in update fields",
Method: code.Method{
Name: "UpdateAgeAndAndGenderByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidUpdateFields,
},
{
Name: "push operator in non-array field",
Method: code.Method{
Name: "UpdateGenderPushByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedError: spec.NewIncompatibleUpdateOperatorError(spec.UpdateOperatorPush, spec.FieldReference{
code.StructField{
Name: "Gender",
Type: code.SimpleType("Gender"),
},
}),
},
{
Name: "inc operator in non-number field",
Method: code.Method{
Name: "UpdateCityIncByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedError: spec.NewIncompatibleUpdateOperatorError(spec.UpdateOperatorInc, spec.FieldReference{
code.StructField{
Name: "City",
Type: code.TypeString,
},
}),
},
{
Name: "update method without query",
Method: code.Method{
Name: "UpdateCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedError: spec.ErrQueryRequired,
},
{
Name: "ambiguous query",
Method: code.Method{
Name: "UpdateAgeByIDAndUsernameOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"ID", "And", "Username", "Or", "Gender"}),
},
{
Name: "parameters for push operator is not array's contained type",
Method: code.Method{
Name: "UpdateConsentHistoryPushByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ArrayType{ContainedType: code.SimpleType("ConsentHistoryItem")}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError(
consentHistoryField.Name,
code.SimpleType("ConsentHistoryItem"),
code.ArrayType{
ContainedType: code.SimpleType("ConsentHistoryItem"),
},
),
},
{
Name: "insufficient function parameters",
Method: code.Method{
Name: "UpdateEnabledAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
// {Type: code.SimpleType("Enabled")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidUpdateFields,
},
{
Name: "update model with invalid parameter",
Method: code.Method{
Name: "UpdateByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeBool,
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidUpdateFields,
},
{
Name: "no context parameter",
Method: code.Method{
Name: "UpdateAgeByGender",
Params: []code.Param{
{Type: code.TypeInt},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrContextParamRequired,
},
{
Name: "struct field not found in update fields",
Method: code.Method{
Name: "UpdateCountryByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
{
Name: "struct field does not match parameter in update fields",
Method: code.Method{
Name: "UpdateAgeByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeFloat64},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError(ageField.Name, ageField.Type, code.TypeFloat64),
},
{
Name: "struct field does not match parameter in query",
Method: code.Method{
Name: "UpdateAgeByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeInt},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError(genderField.Name, genderField.Type, code.TypeString),
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err.Error() != testCase.ExpectedError.Error() {
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
}
})
}
}
func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "invalid number of returns",
Method: code.Method{
Name: "DeleteByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewOperationReturnCountUnmatchedError(2),
},
{
Name: "unsupported return types from delete method",
Method: code.Method{
Name: "DeleteByID",
Returns: []code.Type{
code.TypeFloat64,
code.TypeError,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.TypeFloat64, 0),
},
{
Name: "error return not provided",
Method: code.Method{
Name: "DeleteByID",
Returns: []code.Type{
code.TypeInt,
code.TypeBool,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.TypeBool, 1),
},
{
Name: "delete method without query",
Method: code.Method{
Name: "Delete",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrQueryRequired,
},
{
Name: "misplaced operator token (leftmost)",
Method: code.Method{
Name: "DeleteByAndGender",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"And", "Gender"}),
},
{
Name: "misplaced operator token (rightmost)",
Method: code.Method{
Name: "DeleteByGenderAnd",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And"}),
},
{
Name: "misplaced operator token (double operator)",
Method: code.Method{
Name: "DeleteByGenderAndAndCity",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "And", "City"}),
},
{
Name: "ambiguous query",
Method: code.Method{
Name: "DeleteByGenderAndCityOrAge",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "City", "Or", "Age"}),
},
{
Name: "no context parameter",
Method: code.Method{
Name: "DeleteByGender",
Params: []code.Param{
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrContextParamRequired,
},
{
Name: "mismatched number of parameters",
Method: code.Method{
Name: "DeleteByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidParam,
},
{
Name: "struct field not found",
Method: code.Method{
Name: "DeleteByCountry",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
{
Name: "mismatched method parameter type",
Method: code.Method{
Name: "DeleteByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError("Gender", code.SimpleType("Gender"), code.TypeString),
},
{
Name: "mismatched method parameter type for special case",
Method: code.Method{
Name: "DeleteByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError("City",
code.ArrayType{ContainedType: code.TypeString}, code.TypeString),
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err.Error() != testCase.ExpectedError.Error() {
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
}
})
}
}
func TestParseInterfaceMethod_Count_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "invalid number of returns",
Method: code.Method{
Name: "CountAll",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
code.TypeBool,
},
},
ExpectedError: spec.NewOperationReturnCountUnmatchedError(2),
},
{
Name: "invalid integer return",
Method: code.Method{
Name: "CountAll",
Returns: []code.Type{
code.SimpleType("int64"),
code.TypeError,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.SimpleType("int64"), 0),
},
{
Name: "error return not provided",
Method: code.Method{
Name: "CountAll",
Returns: []code.Type{
code.TypeInt,
code.TypeBool,
},
},
ExpectedError: spec.NewUnsupportedReturnError(code.TypeBool, 1),
},
{
Name: "count method without query",
Method: code.Method{
Name: "Count",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrQueryRequired,
},
{
Name: "invalid query",
Method: code.Method{
Name: "CountBy",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By"}),
},
{
Name: "context parameter not provided",
Method: code.Method{
Name: "CountAll",
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrContextParamRequired,
},
{
Name: "mismatched number of parameter",
Method: code.Method{
Name: "CountByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("Gender")},
{Type: code.TypeInt},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.ErrInvalidParam,
},
{
Name: "mismatched method parameter type",
Method: code.Method{
Name: "CountByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewArgumentTypeNotMatchedError("Gender", code.SimpleType("Gender"), code.TypeString),
},
{
Name: "struct field not found",
Method: code.Method{
Name: "CountByCountry",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.TypeString},
},
Returns: []code.Type{
code.TypeInt,
code.TypeError,
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err.Error() != testCase.ExpectedError.Error() {
t.Errorf("\nExpected = %+v\nReceived = %+v", testCase.ExpectedError, err)
}
})
}
}