Files
atomctl/pkg/swag/generics_test.go
2024-12-25 16:18:41 +08:00

457 lines
14 KiB
Go

//go:build go1.18
// +build go1.18
package swag
import (
"encoding/json"
"fmt"
"go/ast"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
type testLogger struct {
Messages []string
}
func (t *testLogger) Printf(format string, v ...interface{}) {
t.Messages = append(t.Messages, fmt.Sprintf(format, v...))
}
func TestParseGenericsBasic(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_basic"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
p.Overrides = map[string]string{
"types.Field[string]": "string",
"types.DoubleField[string,string]": "[]string",
"types.TrippleField[string,string]": "[][]string",
}
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsArrays(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_arrays"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsNested(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_nested"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsMultiLevelNesting(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_multi_level_nesting"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsProperty(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_property"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsNames(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_names"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsPackageAlias(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_package_alias/internal"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New(SetParseDependency(1))
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsFunctionScoped(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_function_scoped"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParametrizeStruct(t *testing.T) {
pd := PackagesDefinitions{
packages: make(map[string]*PackageDefinitions),
uniqueDefinitions: make(map[string]*TypeSpecDef),
}
// valid
typeSpec := pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
File: &ast.File{Name: &ast.Ident{Name: "test"}},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}, "test.Field[string, []string]")
assert.NotNil(t, typeSpec)
assert.Equal(t, "$test.Field-string-array_string", typeSpec.Name())
assert.Equal(t, "test.Field-string-array_string", typeSpec.TypeName())
// definition contains one type params, but two type params are provided
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}, "test.Field[string, string]")
assert.Nil(t, typeSpec)
// definition contains two type params, but only one is used
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}, "test.Field[string]")
assert.Nil(t, typeSpec)
// name is not a valid type name
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}, "test.Field[string")
assert.Nil(t, typeSpec)
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}, "test.Field[string, [string]")
assert.Nil(t, typeSpec)
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}, "test.Field[string, ]string]")
assert.Nil(t, typeSpec)
}
func TestSplitGenericsTypeNames(t *testing.T) {
t.Parallel()
field, params := splitGenericsTypeName("test.Field")
assert.Empty(t, field)
assert.Nil(t, params)
field, params = splitGenericsTypeName("test.Field]")
assert.Empty(t, field)
assert.Nil(t, params)
field, params = splitGenericsTypeName("test.Field[string")
assert.Empty(t, field)
assert.Nil(t, params)
field, params = splitGenericsTypeName("test.Field[string] ")
assert.Equal(t, "test.Field", field)
assert.Equal(t, []string{"string"}, params)
field, params = splitGenericsTypeName("test.Field[string, []string]")
assert.Equal(t, "test.Field", field)
assert.Equal(t, []string{"string", "[]string"}, params)
field, params = splitGenericsTypeName("test.Field[test.Field[ string, []string] ]")
assert.Equal(t, "test.Field", field)
assert.Equal(t, []string{"test.Field[string,[]string]"}, params)
}
func TestGetGenericFieldType(t *testing.T) {
field, err := getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "Field[string]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.Ident{Name: "int"}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string,int]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.ArrayType{Elt: &ast.Ident{Name: "int"}}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string,[]int]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.BadExpr{},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.Ident{Name: "int"}},
},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.ArrayType{Elt: &ast.BadExpr{}}},
},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.Ident{Name: "Field"}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string]", field)
field, err = getFieldType(
&ast.File{Name: nil},
&ast.IndexExpr{X: &ast.Ident{Name: "Field"}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.BadExpr{}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.Ident{Name: "Field"}, Index: &ast.BadExpr{}},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "field"}, Sel: &ast.Ident{Name: "Name"}}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "field.Name[string]", field)
}
func TestGetGenericTypeName(t *testing.T) {
field, err := getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
)
assert.NoError(t, err)
assert.Equal(t, "test.Field", field)
field, err = getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.ArrayType{Elt: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}}},
)
assert.NoError(t, err)
assert.Equal(t, "test.Field", field)
field, err = getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.SelectorExpr{X: &ast.Ident{Name: "field"}, Sel: &ast.Ident{Name: "Name"}},
)
assert.NoError(t, err)
assert.Equal(t, "field.Name", field)
_, err = getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.BadExpr{},
)
assert.Error(t, err)
}
func TestParseGenericTypeExpr(t *testing.T) {
t.Parallel()
parser := New()
logger := &testLogger{}
SetDebugger(logger)(parser)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.InterfaceType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.StructType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.Ident{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.StarExpr{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.SelectorExpr{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.ArrayType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.MapType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.FuncType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.BadExpr{})
assert.NotEmpty(t, logger.Messages)
assert.Len(t, logger.Messages, 1)
parser.packages.uniqueDefinitions["field.Name[string]"] = &TypeSpecDef{
File: &ast.File{Name: &ast.Ident{Name: "test"}},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}
spec, err := parser.parseTypeExpr(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "field"}, Sel: &ast.Ident{Name: "Name"}}, Index: &ast.Ident{Name: "string"}},
false,
)
assert.NotNil(t, spec)
assert.NoError(t, err)
logger.Messages = []string{}
spec, err = parser.parseTypeExpr(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.BadExpr{}, Index: &ast.Ident{Name: "string"}},
false,
)
assert.NotNil(t, spec)
assert.Equal(t, "object", spec.SchemaProps.Type[0])
assert.NotEmpty(t, logger.Messages)
assert.Len(t, logger.Messages, 1)
logger.Messages = []string{}
spec, err = parser.parseTypeExpr(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.BadExpr{},
false,
)
assert.NotNil(t, spec)
assert.Equal(t, "object", spec.SchemaProps.Type[0])
assert.NotEmpty(t, logger.Messages)
assert.Len(t, logger.Messages, 1)
}