987 lines
32 KiB
Go
987 lines
32 KiB
Go
package system
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// getProjectRoot returns the project root directory
|
|
func getProjectRoot() string {
|
|
_, filename, _, _ := runtime.Caller(0)
|
|
// Go up from internal/biz/system to project root
|
|
return filepath.Join(filepath.Dir(filename), "..", "..", "..")
|
|
}
|
|
|
|
// Feature: gva-to-kratos-migration, Property 1: Migration Completeness
|
|
// *For any* GVA source file containing functions, the corresponding KRA target file
|
|
// SHALL contain equivalent functions with matching signatures.
|
|
// **Validates: Requirements 1.1, 1.2, 2.1, 2.2, 3.1, 3.2**
|
|
|
|
// TestMigrationCompleteness_ServiceLayer tests that all GVA service functions are migrated to KRA biz layer
|
|
func TestMigrationCompleteness_ServiceLayer(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Define mapping from GVA service files to KRA biz files
|
|
serviceMappings := map[string]string{
|
|
filepath.Join(root, "gva/server/service/system/sys_user.go"): filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_api.go"): filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_authority.go"): filepath.Join(root, "internal/biz/system/authority.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_menu.go"): filepath.Join(root, "internal/biz/system/menu.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_base_menu.go"): filepath.Join(root, "internal/biz/system/menu.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_casbin.go"): filepath.Join(root, "internal/biz/system/casbin.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_dictionary.go"): filepath.Join(root, "internal/biz/system/dictionary.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_dictionary_detail.go"): filepath.Join(root, "internal/biz/system/dictionary.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_operation_record.go"): filepath.Join(root, "internal/biz/system/operation_record.go"),
|
|
filepath.Join(root, "gva/server/service/system/jwt_black_list.go"): filepath.Join(root, "internal/biz/system/jwt_blacklist.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_export_template.go"): filepath.Join(root, "internal/biz/system/export_template.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_params.go"): filepath.Join(root, "internal/biz/system/params.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_system.go"): filepath.Join(root, "internal/biz/system/system.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_version.go"): filepath.Join(root, "internal/biz/system/version.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_error.go"): filepath.Join(root, "internal/biz/system/error.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_authority_btn.go"): filepath.Join(root, "internal/biz/system/authority_btn.go"),
|
|
}
|
|
|
|
for gvaFile, kraFile := range serviceMappings {
|
|
t.Run(filepath.Base(gvaFile), func(t *testing.T) {
|
|
gvaFuncs := extractPublicFunctions(t, gvaFile)
|
|
kraFuncs := extractPublicFunctions(t, kraFile)
|
|
|
|
// Check that KRA file exists
|
|
if _, err := os.Stat(kraFile); os.IsNotExist(err) {
|
|
t.Errorf("KRA file %s does not exist for GVA file %s", kraFile, gvaFile)
|
|
return
|
|
}
|
|
|
|
// For each GVA function, check if equivalent exists in KRA
|
|
for funcName := range gvaFuncs {
|
|
// Skip internal/helper functions and struct methods that may have different names
|
|
if strings.HasPrefix(funcName, "get") || strings.HasPrefix(funcName, "set") {
|
|
continue
|
|
}
|
|
// Check if function exists (may have different naming convention)
|
|
found := false
|
|
for kraFunc := range kraFuncs {
|
|
if strings.EqualFold(funcName, kraFunc) || strings.Contains(strings.ToLower(kraFunc), strings.ToLower(funcName)) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Logf("Function %s from %s may need migration to %s", funcName, gvaFile, kraFile)
|
|
}
|
|
}
|
|
|
|
// Verify KRA file has functions
|
|
assert.NotEmpty(t, kraFuncs, "KRA file %s should have functions", kraFile)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMigrationCompleteness_AutoCodeLayer tests AutoCode functionality migration
|
|
func TestMigrationCompleteness_AutoCodeLayer(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
autoCodeMappings := map[string]string{
|
|
filepath.Join(root, "gva/server/service/system/auto_code_template.go"): filepath.Join(root, "internal/biz/system/auto_code_template.go"),
|
|
filepath.Join(root, "gva/server/service/system/auto_code_package.go"): filepath.Join(root, "internal/biz/system/auto_code_package.go"),
|
|
filepath.Join(root, "gva/server/service/system/auto_code_history.go"): filepath.Join(root, "internal/biz/system/auto_code_history.go"),
|
|
filepath.Join(root, "gva/server/service/system/auto_code_plugin.go"): filepath.Join(root, "internal/biz/system/auto_code_plugin.go"),
|
|
filepath.Join(root, "gva/server/service/system/auto_code_llm.go"): filepath.Join(root, "internal/biz/system/auto_code_llm.go"),
|
|
filepath.Join(root, "gva/server/service/system/auto_code_mcp.go"): filepath.Join(root, "internal/biz/system/auto_code_mcp.go"),
|
|
}
|
|
|
|
for gvaFile, kraFile := range autoCodeMappings {
|
|
t.Run(filepath.Base(gvaFile), func(t *testing.T) {
|
|
// Check KRA file exists
|
|
if _, err := os.Stat(kraFile); os.IsNotExist(err) {
|
|
t.Errorf("KRA AutoCode file %s does not exist", kraFile)
|
|
return
|
|
}
|
|
|
|
kraFuncs := extractPublicFunctions(t, kraFile)
|
|
assert.NotEmpty(t, kraFuncs, "KRA AutoCode file %s should have functions", kraFile)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestMigrationCompleteness_InitDBLayer tests InitDB functionality migration
|
|
func TestMigrationCompleteness_InitDBLayer(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
initDBMappings := map[string]string{
|
|
filepath.Join(root, "gva/server/service/system/sys_initdb.go"): filepath.Join(root, "internal/biz/system/initdb.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_initdb_mysql.go"): filepath.Join(root, "internal/biz/system/initdb_mysql.go"),
|
|
filepath.Join(root, "gva/server/service/system/sys_initdb_pgsql.go"): filepath.Join(root, "internal/biz/system/initdb_pgsql.go"),
|
|
}
|
|
|
|
for gvaFile, kraFile := range initDBMappings {
|
|
t.Run(filepath.Base(gvaFile), func(t *testing.T) {
|
|
if _, err := os.Stat(kraFile); os.IsNotExist(err) {
|
|
t.Errorf("KRA InitDB file %s does not exist", kraFile)
|
|
return
|
|
}
|
|
|
|
kraFuncs := extractPublicFunctions(t, kraFile)
|
|
assert.NotEmpty(t, kraFuncs, "KRA InitDB file %s should have functions", kraFile)
|
|
})
|
|
}
|
|
}
|
|
|
|
// extractPublicFunctions extracts all public function names from a Go file
|
|
func extractPublicFunctions(t *testing.T, filePath string) map[string]bool {
|
|
t.Helper()
|
|
funcs := make(map[string]bool)
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return funcs
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return funcs
|
|
}
|
|
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.FuncDecl:
|
|
// Only include public functions (starting with uppercase)
|
|
if x.Name != nil && len(x.Name.Name) > 0 {
|
|
firstChar := x.Name.Name[0]
|
|
if firstChar >= 'A' && firstChar <= 'Z' {
|
|
funcs[x.Name.Name] = true
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return funcs
|
|
}
|
|
|
|
// Feature: gva-to-kratos-migration, Property 2: Logic Preservation
|
|
// *For any* migrated function, given the same input parameters, the function
|
|
// SHALL produce equivalent output (accounting for framework-specific adaptations).
|
|
// **Validates: Requirements 1.3, 2.3, 4.2, 5.4**
|
|
|
|
// TestLogicPreservation_FunctionSignatures tests that function signatures are preserved
|
|
func TestLogicPreservation_FunctionSignatures(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Test cases for function signature preservation
|
|
testCases := []struct {
|
|
name string
|
|
gvaFile string
|
|
kraFile string
|
|
funcNames []string // Functions that should have similar signatures
|
|
}{
|
|
{
|
|
name: "User Service Functions",
|
|
gvaFile: filepath.Join(root, "gva/server/service/system/sys_user.go"),
|
|
kraFile: filepath.Join(root, "internal/biz/system/user.go"),
|
|
funcNames: []string{"Register", "Login", "ChangePassword", "ResetPassword"},
|
|
},
|
|
{
|
|
name: "API Service Functions",
|
|
gvaFile: filepath.Join(root, "gva/server/service/system/sys_api.go"),
|
|
kraFile: filepath.Join(root, "internal/biz/system/api.go"),
|
|
funcNames: []string{"CreateApi", "DeleteApi", "UpdateApi"},
|
|
},
|
|
{
|
|
name: "Authority Service Functions",
|
|
gvaFile: filepath.Join(root, "gva/server/service/system/sys_authority.go"),
|
|
kraFile: filepath.Join(root, "internal/biz/system/authority.go"),
|
|
funcNames: []string{"CreateAuthority", "DeleteAuthority", "UpdateAuthority"},
|
|
},
|
|
{
|
|
name: "Menu Service Functions",
|
|
gvaFile: filepath.Join(root, "gva/server/service/system/sys_menu.go"),
|
|
kraFile: filepath.Join(root, "internal/biz/system/menu.go"),
|
|
funcNames: []string{"AddBaseMenu", "DeleteBaseMenu", "UpdateBaseMenu"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
gvaFuncs := extractFunctionSignatures(t, tc.gvaFile)
|
|
kraFuncs := extractFunctionSignatures(t, tc.kraFile)
|
|
|
|
for _, funcName := range tc.funcNames {
|
|
gvaSig, gvaExists := gvaFuncs[funcName]
|
|
kraSig, kraExists := kraFuncs[funcName]
|
|
|
|
if gvaExists && kraExists {
|
|
// Both exist, log for comparison
|
|
t.Logf("Function %s - GVA params: %d, KRA params: %d", funcName, gvaSig.paramCount, kraSig.paramCount)
|
|
} else if gvaExists && !kraExists {
|
|
// Check if there's a similar function with different naming
|
|
found := false
|
|
for kraFunc := range kraFuncs {
|
|
if strings.Contains(strings.ToLower(kraFunc), strings.ToLower(funcName)) {
|
|
found = true
|
|
t.Logf("Function %s found as %s in KRA", funcName, kraFunc)
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Logf("Function %s exists in GVA but not found in KRA", funcName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify KRA file has functions
|
|
assert.NotEmpty(t, kraFuncs, "KRA file should have functions")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLogicPreservation_MethodReceivers tests that method receivers are properly converted
|
|
func TestLogicPreservation_MethodReceivers(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// In GVA, services are struct methods. In KRA, they should be Usecase methods
|
|
kraFiles := []string{
|
|
filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "internal/biz/system/authority.go"),
|
|
filepath.Join(root, "internal/biz/system/menu.go"),
|
|
filepath.Join(root, "internal/biz/system/casbin.go"),
|
|
}
|
|
|
|
for _, kraFile := range kraFiles {
|
|
t.Run(filepath.Base(kraFile), func(t *testing.T) {
|
|
if _, err := os.Stat(kraFile); os.IsNotExist(err) {
|
|
t.Skipf("KRA file %s does not exist", kraFile)
|
|
return
|
|
}
|
|
|
|
methods := extractMethodReceivers(t, kraFile)
|
|
|
|
// Check that methods have proper receivers (Usecase pattern)
|
|
hasUsecaseReceiver := false
|
|
for receiver := range methods {
|
|
if strings.Contains(receiver, "Usecase") || strings.Contains(receiver, "UseCase") {
|
|
hasUsecaseReceiver = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Log the receivers found
|
|
for receiver, count := range methods {
|
|
t.Logf("Receiver %s has %d methods", receiver, count)
|
|
}
|
|
|
|
// KRA should use Usecase pattern or similar DI pattern
|
|
if !hasUsecaseReceiver && len(methods) > 0 {
|
|
t.Logf("Note: %s may not follow Usecase pattern, found receivers: %v", kraFile, methods)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLogicPreservation_ErrorHandling tests that error handling patterns are preserved
|
|
func TestLogicPreservation_ErrorHandling(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Check that KRA biz layer has proper error definitions
|
|
errorFile := filepath.Join(root, "internal/biz/system/error.go")
|
|
|
|
if _, err := os.Stat(errorFile); os.IsNotExist(err) {
|
|
// Check alternative location
|
|
errorFile = filepath.Join(root, "internal/biz/errros.go")
|
|
}
|
|
|
|
if _, err := os.Stat(errorFile); os.IsNotExist(err) {
|
|
t.Skip("Error definition file not found")
|
|
return
|
|
}
|
|
|
|
// Parse the error file and check for error definitions
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, errorFile, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse error file: %v", err)
|
|
}
|
|
|
|
errorCount := 0
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.ValueSpec:
|
|
// Check for error variable declarations
|
|
for _, name := range x.Names {
|
|
if strings.HasPrefix(name.Name, "Err") || strings.HasPrefix(name.Name, "err") {
|
|
errorCount++
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
t.Logf("Found %d error definitions in %s", errorCount, errorFile)
|
|
assert.Greater(t, errorCount, 0, "Should have error definitions for proper error handling")
|
|
}
|
|
|
|
// funcSignature holds basic function signature info
|
|
type funcSignature struct {
|
|
paramCount int
|
|
returnCount int
|
|
}
|
|
|
|
// extractFunctionSignatures extracts function signatures from a Go file
|
|
func extractFunctionSignatures(t *testing.T, filePath string) map[string]funcSignature {
|
|
t.Helper()
|
|
sigs := make(map[string]funcSignature)
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return sigs
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return sigs
|
|
}
|
|
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.FuncDecl:
|
|
if x.Name != nil && len(x.Name.Name) > 0 {
|
|
firstChar := x.Name.Name[0]
|
|
if firstChar >= 'A' && firstChar <= 'Z' {
|
|
sig := funcSignature{}
|
|
if x.Type.Params != nil {
|
|
sig.paramCount = len(x.Type.Params.List)
|
|
}
|
|
if x.Type.Results != nil {
|
|
sig.returnCount = len(x.Type.Results.List)
|
|
}
|
|
sigs[x.Name.Name] = sig
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return sigs
|
|
}
|
|
|
|
// extractMethodReceivers extracts method receivers and their counts
|
|
func extractMethodReceivers(t *testing.T, filePath string) map[string]int {
|
|
t.Helper()
|
|
receivers := make(map[string]int)
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return receivers
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return receivers
|
|
}
|
|
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.FuncDecl:
|
|
if x.Recv != nil && len(x.Recv.List) > 0 {
|
|
// Get receiver type name
|
|
for _, field := range x.Recv.List {
|
|
switch t := field.Type.(type) {
|
|
case *ast.StarExpr:
|
|
if ident, ok := t.X.(*ast.Ident); ok {
|
|
receivers[ident.Name]++
|
|
}
|
|
case *ast.Ident:
|
|
receivers[t.Name]++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return receivers
|
|
}
|
|
|
|
// Feature: gva-to-kratos-migration, Property 3: Dependency Injection Compliance
|
|
// *For any* KRA component that previously used global variables in GVA,
|
|
// the component SHALL receive dependencies through constructor injection.
|
|
// **Validates: Requirements 1.4, 4.3, 14.1**
|
|
|
|
// TestDependencyInjection_NoGlobalVariables tests that KRA biz layer doesn't use global variables
|
|
func TestDependencyInjection_NoGlobalVariables(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// List of KRA biz files to check
|
|
bizFiles := []string{
|
|
filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "internal/biz/system/authority.go"),
|
|
filepath.Join(root, "internal/biz/system/menu.go"),
|
|
filepath.Join(root, "internal/biz/system/casbin.go"),
|
|
filepath.Join(root, "internal/biz/system/dictionary.go"),
|
|
filepath.Join(root, "internal/biz/system/operation_record.go"),
|
|
filepath.Join(root, "internal/biz/system/jwt_blacklist.go"),
|
|
}
|
|
|
|
// GVA global variable patterns to check for
|
|
globalPatterns := []string{
|
|
"global.GVA_DB",
|
|
"global.GVA_CONFIG",
|
|
"global.GVA_LOG",
|
|
"global.GVA_REDIS",
|
|
"global.BlackCache",
|
|
"global.GVA_ROUTERS",
|
|
}
|
|
|
|
for _, bizFile := range bizFiles {
|
|
t.Run(filepath.Base(bizFile), func(t *testing.T) {
|
|
if _, err := os.Stat(bizFile); os.IsNotExist(err) {
|
|
t.Skipf("File %s does not exist", bizFile)
|
|
return
|
|
}
|
|
|
|
content, err := os.ReadFile(bizFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read file: %v", err)
|
|
}
|
|
|
|
fileContent := string(content)
|
|
for _, pattern := range globalPatterns {
|
|
if strings.Contains(fileContent, pattern) {
|
|
t.Errorf("File %s contains global variable reference: %s", bizFile, pattern)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDependencyInjection_ConstructorPattern tests that KRA uses constructor injection
|
|
func TestDependencyInjection_ConstructorPattern(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// List of KRA biz files that should have New* constructors
|
|
bizFiles := []string{
|
|
filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "internal/biz/system/authority.go"),
|
|
filepath.Join(root, "internal/biz/system/menu.go"),
|
|
filepath.Join(root, "internal/biz/system/casbin.go"),
|
|
}
|
|
|
|
for _, bizFile := range bizFiles {
|
|
t.Run(filepath.Base(bizFile), func(t *testing.T) {
|
|
if _, err := os.Stat(bizFile); os.IsNotExist(err) {
|
|
t.Skipf("File %s does not exist", bizFile)
|
|
return
|
|
}
|
|
|
|
constructors := extractConstructors(t, bizFile)
|
|
|
|
// Should have at least one New* constructor
|
|
assert.NotEmpty(t, constructors, "File %s should have constructor functions", bizFile)
|
|
|
|
// Log constructors found
|
|
for name, params := range constructors {
|
|
t.Logf("Constructor %s has %d parameters", name, params)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDependencyInjection_RepoInterface tests that KRA uses repository interfaces
|
|
func TestDependencyInjection_RepoInterface(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// List of KRA biz files that should define repository interfaces
|
|
bizFiles := []string{
|
|
filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "internal/biz/system/authority.go"),
|
|
filepath.Join(root, "internal/biz/system/menu.go"),
|
|
}
|
|
|
|
for _, bizFile := range bizFiles {
|
|
t.Run(filepath.Base(bizFile), func(t *testing.T) {
|
|
if _, err := os.Stat(bizFile); os.IsNotExist(err) {
|
|
t.Skipf("File %s does not exist", bizFile)
|
|
return
|
|
}
|
|
|
|
interfaces := extractInterfaces(t, bizFile)
|
|
|
|
// Check for Repo interface pattern
|
|
hasRepoInterface := false
|
|
for name := range interfaces {
|
|
if strings.Contains(name, "Repo") || strings.Contains(name, "Repository") {
|
|
hasRepoInterface = true
|
|
t.Logf("Found repository interface: %s", name)
|
|
}
|
|
}
|
|
|
|
if !hasRepoInterface && len(interfaces) > 0 {
|
|
t.Logf("Note: %s has interfaces but no Repo pattern: %v", bizFile, interfaces)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDependencyInjection_WireProviderSet tests that wire provider sets exist
|
|
func TestDependencyInjection_WireProviderSet(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Check for wire.go files
|
|
wireFiles := []string{
|
|
filepath.Join(root, "cmd/kratos-admin/wire.go"),
|
|
}
|
|
|
|
for _, wireFile := range wireFiles {
|
|
t.Run(filepath.Base(wireFile), func(t *testing.T) {
|
|
if _, err := os.Stat(wireFile); os.IsNotExist(err) {
|
|
t.Skipf("Wire file %s does not exist", wireFile)
|
|
return
|
|
}
|
|
|
|
content, err := os.ReadFile(wireFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read wire file: %v", err)
|
|
}
|
|
|
|
fileContent := string(content)
|
|
|
|
// Check for wire.Build or wire.NewSet
|
|
hasWireBuild := strings.Contains(fileContent, "wire.Build") || strings.Contains(fileContent, "wire.NewSet")
|
|
assert.True(t, hasWireBuild, "Wire file should contain wire.Build or wire.NewSet")
|
|
|
|
// Check for provider imports
|
|
hasProviderImport := strings.Contains(fileContent, "google.golang.org/wire") || strings.Contains(fileContent, "github.com/google/wire")
|
|
assert.True(t, hasProviderImport, "Wire file should import wire package")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDependencyInjection_UsecaseStructure tests that Usecase structs have proper dependencies
|
|
func TestDependencyInjection_UsecaseStructure(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
bizFiles := []string{
|
|
filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "internal/biz/system/authority.go"),
|
|
}
|
|
|
|
for _, bizFile := range bizFiles {
|
|
t.Run(filepath.Base(bizFile), func(t *testing.T) {
|
|
if _, err := os.Stat(bizFile); os.IsNotExist(err) {
|
|
t.Skipf("File %s does not exist", bizFile)
|
|
return
|
|
}
|
|
|
|
structs := extractStructFields(t, bizFile)
|
|
|
|
// Check for Usecase structs with dependencies
|
|
for structName, fields := range structs {
|
|
if strings.Contains(structName, "Usecase") || strings.Contains(structName, "UseCase") {
|
|
t.Logf("Usecase struct %s has %d fields", structName, len(fields))
|
|
|
|
// Should have at least one dependency field (repo, logger, etc.)
|
|
assert.NotEmpty(t, fields, "Usecase %s should have dependency fields", structName)
|
|
|
|
for _, field := range fields {
|
|
t.Logf(" - Field: %s", field)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// extractConstructors extracts New* constructor functions and their parameter counts
|
|
func extractConstructors(t *testing.T, filePath string) map[string]int {
|
|
t.Helper()
|
|
constructors := make(map[string]int)
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return constructors
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return constructors
|
|
}
|
|
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.FuncDecl:
|
|
if x.Name != nil && strings.HasPrefix(x.Name.Name, "New") {
|
|
paramCount := 0
|
|
if x.Type.Params != nil {
|
|
paramCount = len(x.Type.Params.List)
|
|
}
|
|
constructors[x.Name.Name] = paramCount
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return constructors
|
|
}
|
|
|
|
// extractInterfaces extracts interface definitions from a Go file
|
|
func extractInterfaces(t *testing.T, filePath string) map[string]int {
|
|
t.Helper()
|
|
interfaces := make(map[string]int)
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return interfaces
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return interfaces
|
|
}
|
|
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.TypeSpec:
|
|
if _, ok := x.Type.(*ast.InterfaceType); ok {
|
|
interfaces[x.Name.Name] = 1
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return interfaces
|
|
}
|
|
|
|
// extractStructFields extracts struct definitions and their field names
|
|
func extractStructFields(t *testing.T, filePath string) map[string][]string {
|
|
t.Helper()
|
|
structs := make(map[string][]string)
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return structs
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return structs
|
|
}
|
|
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.TypeSpec:
|
|
if structType, ok := x.Type.(*ast.StructType); ok {
|
|
var fields []string
|
|
if structType.Fields != nil {
|
|
for _, field := range structType.Fields.List {
|
|
for _, name := range field.Names {
|
|
fields = append(fields, name.Name)
|
|
}
|
|
}
|
|
}
|
|
structs[x.Name.Name] = fields
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
return structs
|
|
}
|
|
|
|
// Feature: gva-to-kratos-migration, Property 4: Import Integrity
|
|
// *For any* KRA source file, all import statements SHALL resolve to valid packages
|
|
// and the project SHALL compile without import errors.
|
|
// **Validates: Requirements 16.1, 16.2, 16.3, 16.4**
|
|
|
|
// TestImportIntegrity_NoGVAReferences tests that KRA files don't reference GVA packages
|
|
func TestImportIntegrity_NoGVAReferences(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Directories to check for GVA references
|
|
dirsToCheck := []string{
|
|
filepath.Join(root, "internal/biz/system"),
|
|
filepath.Join(root, "internal/data/system"),
|
|
filepath.Join(root, "internal/server/handler/system"),
|
|
filepath.Join(root, "internal/server/router/system"),
|
|
filepath.Join(root, "internal/server/middleware"),
|
|
filepath.Join(root, "pkg"),
|
|
}
|
|
|
|
// GVA import patterns that should not exist in KRA
|
|
gvaImportPatterns := []string{
|
|
"gin-vue-admin",
|
|
"github.com/flipped-aurora/gin-vue-admin",
|
|
"gva/server",
|
|
}
|
|
|
|
for _, dir := range dirsToCheck {
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() || !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
|
|
// Skip test files
|
|
if strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
|
|
t.Run(filepath.Base(path), func(t *testing.T) {
|
|
imports := extractImports(t, path)
|
|
|
|
for _, imp := range imports {
|
|
for _, pattern := range gvaImportPatterns {
|
|
if strings.Contains(imp, pattern) {
|
|
t.Errorf("File %s has GVA import reference: %s", path, imp)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
t.Logf("Warning: error walking directory %s: %v", dir, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestImportIntegrity_KRAPackageNaming tests that KRA uses correct package naming
|
|
func TestImportIntegrity_KRAPackageNaming(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Check that KRA files use "kra" module name
|
|
goModFile := filepath.Join(root, "go.mod")
|
|
|
|
content, err := os.ReadFile(goModFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read go.mod: %v", err)
|
|
}
|
|
|
|
// Check module name
|
|
assert.Contains(t, string(content), "module kra", "go.mod should declare module as 'kra'")
|
|
}
|
|
|
|
// TestImportIntegrity_InternalImports tests that internal packages use correct import paths
|
|
func TestImportIntegrity_InternalImports(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Files to check for correct internal imports
|
|
filesToCheck := []string{
|
|
filepath.Join(root, "internal/biz/system/user.go"),
|
|
filepath.Join(root, "internal/biz/system/api.go"),
|
|
filepath.Join(root, "internal/server/handler/system/sys_user.go"),
|
|
filepath.Join(root, "internal/server/router/system/sys_user.go"),
|
|
}
|
|
|
|
for _, file := range filesToCheck {
|
|
if _, err := os.Stat(file); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
t.Run(filepath.Base(file), func(t *testing.T) {
|
|
imports := extractImports(t, file)
|
|
|
|
// Check that internal imports use "kra/" prefix
|
|
for _, imp := range imports {
|
|
if strings.Contains(imp, "internal/") {
|
|
assert.True(t, strings.HasPrefix(imp, "kra/"),
|
|
"Internal import %s should use 'kra/' prefix", imp)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestImportIntegrity_NoCircularImports tests for potential circular import issues
|
|
func TestImportIntegrity_NoCircularImports(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Build import graph for key packages
|
|
packages := map[string][]string{
|
|
"biz": {},
|
|
"data": {},
|
|
"handler": {},
|
|
"router": {},
|
|
}
|
|
|
|
// Check biz layer imports
|
|
bizDir := filepath.Join(root, "internal/biz/system")
|
|
if _, err := os.Stat(bizDir); err == nil {
|
|
filepath.Walk(bizDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
imports := extractImports(t, path)
|
|
packages["biz"] = append(packages["biz"], imports...)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Check data layer imports
|
|
dataDir := filepath.Join(root, "internal/data/system")
|
|
if _, err := os.Stat(dataDir); err == nil {
|
|
filepath.Walk(dataDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
imports := extractImports(t, path)
|
|
packages["data"] = append(packages["data"], imports...)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Verify layering: biz should not import data directly (data implements biz interfaces)
|
|
for _, imp := range packages["biz"] {
|
|
if strings.Contains(imp, "internal/data") {
|
|
t.Logf("Note: biz layer imports data layer: %s (may be intentional for model types)", imp)
|
|
}
|
|
}
|
|
|
|
// Verify handler imports biz but not data directly
|
|
handlerDir := filepath.Join(root, "internal/server/handler/system")
|
|
if _, err := os.Stat(handlerDir); err == nil {
|
|
filepath.Walk(handlerDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
imports := extractImports(t, path)
|
|
for _, imp := range imports {
|
|
if strings.Contains(imp, "internal/data/system") {
|
|
t.Logf("Note: handler imports data layer directly: %s in %s", imp, path)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestImportIntegrity_CompilationCheck tests that the project compiles successfully
|
|
func TestImportIntegrity_CompilationCheck(t *testing.T) {
|
|
// This test verifies that the test file itself compiles,
|
|
// which implicitly tests that all imports in the package are valid
|
|
|
|
// If we reach this point, the package compiled successfully
|
|
t.Log("Package compiled successfully - all imports are valid")
|
|
}
|
|
|
|
// TestImportIntegrity_PackageDeclarations tests that package declarations are correct
|
|
func TestImportIntegrity_PackageDeclarations(t *testing.T) {
|
|
root := getProjectRoot()
|
|
|
|
// Map of directories to expected package names
|
|
packageExpectations := map[string]string{
|
|
filepath.Join(root, "internal/biz/system"): "system",
|
|
filepath.Join(root, "internal/data/system"): "system",
|
|
filepath.Join(root, "internal/server/handler/system"): "system",
|
|
filepath.Join(root, "internal/server/router/system"): "system",
|
|
filepath.Join(root, "internal/server/middleware"): "middleware",
|
|
filepath.Join(root, "pkg/utils"): "utils",
|
|
filepath.Join(root, "pkg/jwt"): "jwt",
|
|
filepath.Join(root, "pkg/auth"): "auth",
|
|
}
|
|
|
|
for dir, expectedPkg := range packageExpectations {
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
t.Run(filepath.Base(dir), func(t *testing.T) {
|
|
// Find first .go file in directory
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Skipf("Cannot read directory %s: %v", dir, err)
|
|
return
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
|
|
continue
|
|
}
|
|
|
|
filePath := filepath.Join(dir, entry.Name())
|
|
pkgName := extractPackageName(t, filePath)
|
|
|
|
assert.Equal(t, expectedPkg, pkgName,
|
|
"File %s should have package name '%s', got '%s'", filePath, expectedPkg, pkgName)
|
|
break // Only check first file
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// extractImports extracts all import paths from a Go file
|
|
func extractImports(t *testing.T, filePath string) []string {
|
|
t.Helper()
|
|
var imports []string
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return imports
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ImportsOnly)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return imports
|
|
}
|
|
|
|
for _, imp := range node.Imports {
|
|
if imp.Path != nil {
|
|
// Remove quotes from import path
|
|
path := strings.Trim(imp.Path.Value, "\"")
|
|
imports = append(imports, path)
|
|
}
|
|
}
|
|
|
|
return imports
|
|
}
|
|
|
|
// extractPackageName extracts the package name from a Go file
|
|
func extractPackageName(t *testing.T, filePath string) string {
|
|
t.Helper()
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
return ""
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.PackageClauseOnly)
|
|
if err != nil {
|
|
t.Logf("Warning: could not parse %s: %v", filePath, err)
|
|
return ""
|
|
}
|
|
|
|
if node.Name != nil {
|
|
return node.Name.Name
|
|
}
|
|
|
|
return ""
|
|
}
|