Files
hm-server/app/main/api/internal/service/authorizationService_test.go
2025-09-21 18:27:25 +08:00

671 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"database/sql"
"errors"
"fmt"
"hm-server/app/main/api/internal/config"
"hm-server/app/main/model"
"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)
}