package service import ( "context" "tydata-server/app/main/api/internal/config" "tydata-server/app/main/model" "database/sql" "errors" "fmt" "os" "path/filepath" "strings" "testing" "time" "github.com/Masterminds/squirrel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/zeromicro/go-zero/core/stores/sqlx" ) // mockResult 模拟sql.Result type mockResult struct { lastInsertId int64 rowsAffected int64 } func (m *mockResult) LastInsertId() (int64, error) { return m.lastInsertId, nil } func (m *mockResult) RowsAffected() (int64, error) { return m.rowsAffected, nil } // MockAuthorizationDocumentModel 模拟授权书模型 type MockAuthorizationDocumentModel struct { mock.Mock } func (m *MockAuthorizationDocumentModel) Insert(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) (sql.Result, error) { args := m.Called(ctx, session, data) return args.Get(0).(sql.Result), args.Error(1) } func (m *MockAuthorizationDocumentModel) FindOne(ctx context.Context, id int64) (*model.AuthorizationDocument, error) { args := m.Called(ctx, id) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*model.AuthorizationDocument), args.Error(1) } func (m *MockAuthorizationDocumentModel) Update(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) (sql.Result, error) { args := m.Called(ctx, session, data) return args.Get(0).(sql.Result), args.Error(1) } func (m *MockAuthorizationDocumentModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) error { args := m.Called(ctx, session, data) return args.Error(0) } func (m *MockAuthorizationDocumentModel) Trans(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { args := m.Called(ctx, fn) return args.Error(0) } func (m *MockAuthorizationDocumentModel) SelectBuilder() squirrel.SelectBuilder { args := m.Called() return args.Get(0).(squirrel.SelectBuilder) } func (m *MockAuthorizationDocumentModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *model.AuthorizationDocument) error { args := m.Called(ctx, session, data) return args.Error(0) } func (m *MockAuthorizationDocumentModel) FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error) { args := m.Called(ctx, sumBuilder, field) return args.Get(0).(float64), args.Error(1) } func (m *MockAuthorizationDocumentModel) FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error) { args := m.Called(ctx, countBuilder, field) return args.Get(0).(int64), args.Error(1) } func (m *MockAuthorizationDocumentModel) FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*model.AuthorizationDocument, error) { args := m.Called(ctx, rowBuilder, orderBy) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) } func (m *MockAuthorizationDocumentModel) FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*model.AuthorizationDocument, error) { args := m.Called(ctx, rowBuilder, page, pageSize, orderBy) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) } func (m *MockAuthorizationDocumentModel) FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*model.AuthorizationDocument, int64, error) { args := m.Called(ctx, rowBuilder, page, pageSize, orderBy) if args.Get(0) == nil { return nil, args.Get(1).(int64), args.Error(2) } return args.Get(0).([]*model.AuthorizationDocument), args.Get(1).(int64), args.Error(2) } func (m *MockAuthorizationDocumentModel) FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*model.AuthorizationDocument, error) { args := m.Called(ctx, rowBuilder, preMinId, pageSize) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) } func (m *MockAuthorizationDocumentModel) FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*model.AuthorizationDocument, error) { args := m.Called(ctx, rowBuilder, preMaxId, pageSize) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) } func (m *MockAuthorizationDocumentModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { args := m.Called(ctx, session, id) return args.Error(0) } func (m *MockAuthorizationDocumentModel) FindByOrderId(ctx context.Context, orderId int64) ([]*model.AuthorizationDocument, error) { args := m.Called(ctx, orderId) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).([]*model.AuthorizationDocument), args.Error(1) } // MockResult 模拟数据库结果 type MockResult struct { lastInsertId int64 } func (m *MockResult) LastInsertId() (int64, error) { return m.lastInsertId, nil } func (m *MockResult) RowsAffected() (int64, error) { return 1, nil } // TestNewAuthorizationService 测试创建授权书服务 func TestNewAuthorizationService(t *testing.T) { config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.com/api/v1/auth-docs", }, } mockModel := &MockAuthorizationDocumentModel{} service := NewAuthorizationService(config, mockModel) assert.NotNil(t, service) assert.Equal(t, "data/authorization_docs", service.fileStoragePath) assert.Equal(t, "https://test.com/api/v1/auth-docs", service.fileBaseURL) } // TestGetFullFileURL 测试获取完整文件URL func TestGetFullFileURL(t *testing.T) { config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.com/api/v1/auth-docs", }, } mockModel := &MockAuthorizationDocumentModel{} service := NewAuthorizationService(config, mockModel) tests := []struct { name string relativePath string expected string }{ { name: "正常相对路径", relativePath: "2025/09/auth_123_456_20250913_160800.pdf", expected: "https://test.com/api/v1/auth-docs/2025/09/auth_123_456_20250913_160800.pdf", }, { name: "空路径", relativePath: "", expected: "", }, { name: "只有文件名", relativePath: "test.pdf", expected: "https://test.com/api/v1/auth-docs/test.pdf", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := service.GetFullFileURL(tt.relativePath) assert.Equal(t, tt.expected, result) }) } } // TestGenerateAuthorizationDocument 测试生成授权书 func TestGenerateAuthorizationDocument(t *testing.T) { // 创建测试配置 config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.example.com/api/v1/auth-docs", }, } // 创建模拟的数据库模型 mockModel := &MockAuthorizationDocumentModel{} // 创建授权书服务 service := NewAuthorizationService(config, mockModel) // 准备测试数据 userInfo := map[string]interface{}{ "name": "张三", "id_card": "110101199001011234", "mobile": "13800138000", } // 模拟数据库插入成功 mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( &mockResult{lastInsertId: 1, rowsAffected: 1}, nil) // 执行测试 authDoc, err := service.GenerateAuthorizationDocument( context.Background(), 1, // userID 2, // orderID 3, // queryID userInfo, ) // 验证结果 assert.NoError(t, err) assert.NotNil(t, authDoc) assert.Equal(t, int64(1), authDoc.UserId) assert.Equal(t, int64(2), authDoc.OrderId) assert.Equal(t, int64(3), authDoc.QueryId) assert.Equal(t, "pdf", authDoc.FileType) assert.Equal(t, "active", authDoc.Status) assert.False(t, authDoc.ExpireTime.Valid) // 永久保留,不设置过期时间 // 验证文件路径格式(兼容Windows和Unix路径分隔符) assert.True(t, strings.Contains(authDoc.FilePath, "data/authorization_docs") || strings.Contains(authDoc.FilePath, "data\\authorization_docs")) assert.Contains(t, authDoc.FileName, "auth_") assert.Contains(t, authDoc.FileName, ".pdf") // 验证相对路径格式 assert.Regexp(t, `^\d{4}/\d{2}/auth_\d+_\d+_\d{8}_\d{6}\.pdf$`, authDoc.FileUrl) // 验证文件大小 assert.Greater(t, authDoc.FileSize, int64(0)) // 验证数据库调用 mockModel.AssertExpectations(t) // 验证文件是否真的被创建 if _, err := os.Stat(authDoc.FilePath); err == nil { t.Logf("✅ 授权书文件已创建: %s", authDoc.FilePath) t.Logf("📊 文件大小: %d 字节", authDoc.FileSize) } else { t.Logf("⚠️ 文件未找到: %s", authDoc.FilePath) } } // TestGenerateAuthorizationDocument_DatabaseError 测试数据库错误 func TestGenerateAuthorizationDocument_DatabaseError(t *testing.T) { // 创建测试配置 config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.example.com/api/v1/auth-docs", }, } // 创建模拟的数据库模型 mockModel := &MockAuthorizationDocumentModel{} // 创建授权书服务 service := NewAuthorizationService(config, mockModel) // 准备测试数据 userInfo := map[string]interface{}{ "name": "李四", "id_card": "110101199001011235", "mobile": "13800138001", } // 模拟数据库插入失败 mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( (*mockResult)(nil), errors.New("数据库连接失败")) // 执行测试 authDoc, err := service.GenerateAuthorizationDocument( context.Background(), 1, // userID 2, // orderID 3, // queryID userInfo, ) // 验证结果 assert.Error(t, err) assert.Nil(t, authDoc) assert.Contains(t, err.Error(), "数据库连接失败") // 验证数据库调用 mockModel.AssertExpectations(t) } // TestGeneratePDFContent 测试生成PDF内容 func TestGeneratePDFContent(t *testing.T) { config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.com/api/v1/auth-docs", }, } mockModel := &MockAuthorizationDocumentModel{} service := NewAuthorizationService(config, mockModel) userInfo := map[string]interface{}{ "name": "张三", "id_card": "110101199001011234", } pdfBytes, err := service.generatePDFContent(userInfo) // 验证结果 assert.NoError(t, err) assert.NotNil(t, pdfBytes) assert.Greater(t, len(pdfBytes), 0) // 验证PDF内容(PDF是二进制格式,只验证基本结构) assert.Contains(t, string(pdfBytes), "%PDF") // PDF文件头 // 按照 GenerateAuthorizationDocument 的方式保存文件到本地 // 1. 创建文件存储目录 year := time.Now().Format("2006") month := time.Now().Format("01") dirPath := filepath.Join("data", "authorization_docs", year, month) if err := os.MkdirAll(dirPath, 0755); err != nil { t.Fatalf("创建存储目录失败: %v", err) } // 2. 生成文件名和路径 fileName := fmt.Sprintf("test_auth_%s.pdf", time.Now().Format("20060102_150405")) filePath := filepath.Join(dirPath, fileName) relativePath := fmt.Sprintf("%s/%s/%s", year, month, fileName) // 3. 保存PDF文件 if err := os.WriteFile(filePath, pdfBytes, 0644); err != nil { t.Fatalf("保存PDF文件失败: %v", err) } // 4. 验证文件是否保存成功 if _, err := os.Stat(filePath); err == nil { t.Logf("✅ PDF文件已保存到本地") t.Logf("📄 文件名: %s", fileName) t.Logf("📁 文件路径: %s", filePath) t.Logf("🔗 相对路径: %s", relativePath) t.Logf("📊 文件大小: %d 字节", len(pdfBytes)) // 获取绝对路径 absPath, _ := filepath.Abs(filePath) t.Logf("📍 绝对路径: %s", absPath) } else { t.Errorf("❌ PDF文件保存失败: %v", err) } } // TestSavePDFToLocal 专门测试保存PDF文件到本地 func TestSavePDFToLocal(t *testing.T) { config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.com/api/v1/auth-docs", }, } mockModel := &MockAuthorizationDocumentModel{} service := NewAuthorizationService(config, mockModel) // 准备测试数据 userInfo := map[string]interface{}{ "name": "何志勇", "id_card": "452528197907133014", "mobile": "18276151590", } // 生成PDF内容 pdfBytes, err := service.generatePDFContent(userInfo) assert.NoError(t, err) assert.NotNil(t, pdfBytes) assert.Greater(t, len(pdfBytes), 0) // 按照 GenerateAuthorizationDocument 的方式保存文件到本地 // 1. 创建文件存储目录 year := time.Now().Format("2006") month := time.Now().Format("01") dirPath := filepath.Join("data", "authorization_docs", year, month) if err := os.MkdirAll(dirPath, 0755); err != nil { t.Fatalf("创建存储目录失败: %v", err) } // 2. 生成文件名和路径 fileName := fmt.Sprintf("local_test_auth_%s.pdf", time.Now().Format("20060102_150405")) filePath := filepath.Join(dirPath, fileName) relativePath := fmt.Sprintf("%s/%s/%s", year, month, fileName) // 3. 保存PDF文件 if err := os.WriteFile(filePath, pdfBytes, 0644); err != nil { t.Fatalf("保存PDF文件失败: %v", err) } // 4. 验证文件是否保存成功 if _, err := os.Stat(filePath); err == nil { t.Logf("✅ PDF文件已保存到本地") t.Logf("📄 文件名: %s", fileName) t.Logf("📁 文件路径: %s", filePath) t.Logf("🔗 相对路径: %s", relativePath) t.Logf("📊 文件大小: %d 字节", len(pdfBytes)) // 获取绝对路径 absPath, _ := filepath.Abs(filePath) t.Logf("📍 绝对路径: %s", absPath) // 验证文件内容 fileInfo, err := os.Stat(filePath) assert.NoError(t, err) assert.Greater(t, fileInfo.Size(), int64(1000)) // 文件应该大于1KB t.Logf("🎉 文件保存验证通过!") } else { t.Errorf("❌ PDF文件保存失败: %v", err) } } // TestGeneratePDFContent_EmptyUserInfo 测试空用户信息 func TestGeneratePDFContent_EmptyUserInfo(t *testing.T) { config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.com/api/v1/auth-docs", }, } mockModel := &MockAuthorizationDocumentModel{} service := NewAuthorizationService(config, mockModel) userInfo := map[string]interface{}{} pdfBytes, err := service.generatePDFContent(userInfo) // 验证结果 assert.NoError(t, err) assert.NotNil(t, pdfBytes) assert.Greater(t, len(pdfBytes), 0) // 验证PDF内容(PDF是二进制格式,只验证基本结构) assert.Contains(t, string(pdfBytes), "%PDF") // PDF文件头 } // TestGetUserInfoString 测试获取用户信息字符串 func TestGetUserInfoString(t *testing.T) { userInfo := map[string]interface{}{ "name": "张三", "id_card": "110101199001011234", "age": 30, "empty": "", } tests := []struct { name string key string expected string }{ { name: "正常字符串", key: "name", expected: "张三", }, { name: "身份证号", key: "id_card", expected: "110101199001011234", }, { name: "空字符串", key: "empty", expected: "", }, { name: "不存在的键", key: "not_exist", expected: "", }, { name: "非字符串类型", key: "age", expected: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := getUserInfoString(userInfo, tt.key) assert.Equal(t, tt.expected, result) }) } } // BenchmarkGeneratePDFContent 性能测试 func BenchmarkGeneratePDFContent(b *testing.B) { config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.com/api/v1/auth-docs", }, } mockModel := &MockAuthorizationDocumentModel{} service := NewAuthorizationService(config, mockModel) userInfo := map[string]interface{}{ "name": "张三", "id_card": "110101199001011234", } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := service.generatePDFContent(userInfo) if err != nil { b.Fatal(err) } } } // TestAuthorizationService_Integration 集成测试(需要真实文件系统) func TestAuthorizationService_Integration(t *testing.T) { // 创建测试配置 config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.example.com/api/v1/auth-docs", }, } // 创建模拟的数据库模型 mockModel := &MockAuthorizationDocumentModel{} // 创建授权书服务 service := NewAuthorizationService(config, mockModel) // 准备测试数据 userInfo := map[string]interface{}{ "name": "王五", "id_card": "110101199001011236", "mobile": "13800138002", } // 模拟数据库插入成功 mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( &mockResult{lastInsertId: 1, rowsAffected: 1}, nil) // 执行测试 authDoc, err := service.GenerateAuthorizationDocument( context.Background(), 1, // userID 2, // orderID 3, // queryID userInfo, ) // 验证结果 assert.NoError(t, err) assert.NotNil(t, authDoc) // 测试GetFullFileURL方法 fullURL := service.GetFullFileURL(authDoc.FileUrl) expectedURL := fmt.Sprintf("%s/%s", config.Authorization.FileBaseURL, authDoc.FileUrl) assert.Equal(t, expectedURL, fullURL) // 验证文件是否真的被创建 if _, err := os.Stat(authDoc.FilePath); err == nil { t.Logf("✅ 集成测试成功 - 授权书文件已创建") t.Logf("📄 文件名: %s", authDoc.FileName) t.Logf("📁 文件路径: %s", authDoc.FilePath) t.Logf("🔗 相对路径: %s", authDoc.FileUrl) t.Logf("🌐 完整URL: %s", fullURL) t.Logf("📊 文件大小: %d 字节", authDoc.FileSize) } else { t.Logf("⚠️ 集成测试 - 文件未找到: %s", authDoc.FilePath) } // 验证数据库调用 mockModel.AssertExpectations(t) } // TestGeneratePDFFile 专门测试PDF文件生成 func TestGeneratePDFFile(t *testing.T) { // 创建测试配置 config := config.Config{ Authorization: config.AuthorizationConfig{ FileBaseURL: "https://test.example.com/api/v1/auth-docs", }, } // 创建模拟的数据库模型 mockModel := &MockAuthorizationDocumentModel{} // 创建授权书服务 service := NewAuthorizationService(config, mockModel) // 准备测试数据 userInfo := map[string]interface{}{ "name": "测试用户", "id_card": "110101199001011237", "mobile": "13800138003", } // 模拟数据库插入成功 mockModel.On("Insert", mock.Anything, mock.Anything, mock.Anything).Return( &mockResult{lastInsertId: 1, rowsAffected: 1}, nil) // 执行测试 authDoc, err := service.GenerateAuthorizationDocument( context.Background(), 999, // userID 888, // orderID 777, // queryID userInfo, ) // 验证结果 assert.NoError(t, err) assert.NotNil(t, authDoc) // 验证文件是否真的被创建 if _, err := os.Stat(authDoc.FilePath); err == nil { t.Logf("✅ PDF文件生成成功!") t.Logf("📄 文件名: %s", authDoc.FileName) t.Logf("📁 文件路径: %s", authDoc.FilePath) t.Logf("🔗 相对路径: %s", authDoc.FileUrl) t.Logf("📊 文件大小: %d 字节", authDoc.FileSize) // 验证文件内容 fileInfo, err := os.Stat(authDoc.FilePath) assert.NoError(t, err) assert.Greater(t, fileInfo.Size(), int64(1000)) // 文件应该大于1KB // 验证文件名格式 assert.Regexp(t, `^auth_999_888_\d{8}_\d{6}\.pdf$`, authDoc.FileName) // 验证路径格式 assert.Regexp(t, `^\d{4}/\d{2}/auth_999_888_\d{8}_\d{6}\.pdf$`, authDoc.FileUrl) t.Logf("🎉 所有验证通过!") } else { t.Errorf("❌ PDF文件未创建: %s", authDoc.FilePath) } // 验证数据库调用 mockModel.AssertExpectations(t) }