Files
tyapi-server/internal/infrastructure/external/zhicha/zhicha_test.go
2025-08-25 15:44:06 +08:00

699 lines
16 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 zhicha
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"
)
func TestGenerateSign(t *testing.T) {
service := &ZhichaService{
config: ZhichaConfig{
AppSecret: "test_secret_123",
},
}
timestamp := int64(1640995200) // 2022-01-01 00:00:00
sign := service.generateSign(timestamp)
if sign == "" {
t.Error("签名生成失败,签名为空")
}
// 验证签名长度MD5是32位十六进制
if len(sign) != 32 {
t.Errorf("签名长度错误期望32位实际%d位", len(sign))
}
// 验证相同参数生成相同签名
sign2 := service.generateSign(timestamp)
if sign != sign2 {
t.Error("相同参数生成的签名不一致")
}
}
func TestEncryptDecrypt(t *testing.T) {
// 测试密钥32位十六进制
key := "1234567890abcdef1234567890abcdef"
// 测试数据
testData := "这是一个测试数据包含中文和English"
// 加密
encrypted, err := Encrypt(testData, key)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Error("加密结果为空")
}
// 解密
decrypted, err := Decrypt(encrypted, key)
if err != nil {
t.Fatalf("解密失败: %v", err)
}
if decrypted != testData {
t.Errorf("解密结果不匹配,期望: %s, 实际: %s", testData, decrypted)
}
}
func TestEncryptWithInvalidKey(t *testing.T) {
// 测试无效密钥
invalidKeys := []string{
"", // 空密钥
"123", // 太短
"invalid_key_string", // 非十六进制
"1234567890abcdef", // 16位不足32位
}
testData := "test data"
for _, key := range invalidKeys {
_, err := Encrypt(testData, key)
if err == nil {
t.Errorf("使用无效密钥 %s 应该返回错误", key)
}
}
}
func TestDecryptWithInvalidData(t *testing.T) {
key := "1234567890abcdef1234567890abcdef"
// 测试无效的加密数据
invalidData := []string{
"", // 空数据
"invalid_base64", // 无效的Base64
"dGVzdA==", // 有效的Base64但不是AES加密数据
}
for _, data := range invalidData {
_, err := Decrypt(data, key)
if err == nil {
t.Errorf("使用无效数据 %s 应该返回错误", data)
}
}
}
func TestPKCS7Padding(t *testing.T) {
testCases := []struct {
input string
blockSize int
expected int
}{
{"", 16, 16},
{"a", 16, 16},
{"ab", 16, 16},
{"abc", 16, 16},
{"abcd", 16, 16},
{"abcde", 16, 16},
{"abcdef", 16, 16},
{"abcdefg", 16, 16},
{"abcdefgh", 16, 16},
{"abcdefghi", 16, 16},
{"abcdefghij", 16, 16},
{"abcdefghijk", 16, 16},
{"abcdefghijkl", 16, 16},
{"abcdefghijklm", 16, 16},
{"abcdefghijklmn", 16, 16},
{"abcdefghijklmno", 16, 16},
{"abcdefghijklmnop", 16, 16},
}
for _, tc := range testCases {
padded := pkcs7Padding([]byte(tc.input), tc.blockSize)
if len(padded)%tc.blockSize != 0 {
t.Errorf("输入: %s, 期望块大小倍数,实际: %d", tc.input, len(padded))
}
// 测试移除填充
unpadded, err := pkcs7Unpadding(padded)
if err != nil {
t.Errorf("移除填充失败: %v", err)
}
if string(unpadded) != tc.input {
t.Errorf("输入: %s, 期望: %s, 实际: %s", tc.input, tc.input, string(unpadded))
}
}
}
func TestGenerateRequestID(t *testing.T) {
service := &ZhichaService{
config: ZhichaConfig{
AppID: "test_app_id",
},
}
id1 := service.generateRequestID()
// 等待一小段时间确保时间戳不同
time.Sleep(time.Millisecond)
id2 := service.generateRequestID()
if id1 == "" || id2 == "" {
t.Error("请求ID生成失败")
}
if id1 == id2 {
t.Error("不同时间生成的请求ID应该不同")
}
// 验证ID格式
if len(id1) < 20 { // zhicha_ + 8位十六进制 + 其他
t.Errorf("请求ID长度不足实际: %s", id1)
}
}
func TestCallAPISuccess(t *testing.T) {
// 创建测试服务
service := &ZhichaService{
config: ZhichaConfig{
URL: "http://proxy.tianyuanapi.com/dataMiddle/api/handle",
AppID: "4b78fff61ab8426f",
AppSecret: "1128f01b94124ae899c2e9f2b1f37681",
EncryptKey: "af4ca0098e6a202a5c08c413ebd9fd62",
},
logger: nil, // 测试时不使用日志
}
// 测试参数
idCardEncrypted, err := service.Encrypt("45212220000827423X")
if err != nil {
t.Fatalf("加密身份证号失败: %v", err)
}
nameEncrypted, err := service.Encrypt("张荣宏")
if err != nil {
t.Fatalf("加密姓名失败: %v", err)
}
params := map[string]interface{}{
"idCard": idCardEncrypted,
"name": nameEncrypted,
"authorized": "1",
}
// 创建带超时的context
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 调用API
data, err := service.CallAPI(ctx, "ZCI001", params)
// 注意这是真实API调用可能会因为网络、认证等原因失败
// 我们主要测试方法调用是否正常不强制要求API返回成功
if err != nil {
// 如果是网络错误或认证错误,这是正常的
t.Logf("API调用返回错误: %v", err)
return
}
// 如果成功,验证响应
if data == nil {
t.Error("响应数据为空")
return
}
// 将data转换为字符串进行显示
var dataStr string
if str, ok := data.(string); ok {
dataStr = str
} else {
// 如果不是字符串尝试JSON序列化
if dataBytes, err := json.Marshal(data); err == nil {
dataStr = string(dataBytes)
} else {
dataStr = fmt.Sprintf("%v", data)
}
}
t.Logf("API调用成功响应内容: %s", dataStr)
}
func TestCallAPIWithInvalidURL(t *testing.T) {
// 创建使用无效URL的服务
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://invalid-url-that-does-not-exist.com/api",
AppID: "test_app_id",
AppSecret: "test_app_secret",
EncryptKey: "test_encrypt_key",
},
logger: nil,
}
params := map[string]interface{}{
"test": "data",
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 应该返回错误
_, err := service.CallAPI(ctx, "test_pro_id", params)
if err == nil {
t.Error("使用无效URL应该返回错误")
}
t.Logf("预期的错误: %v", err)
}
func TestCallAPIWithContextCancellation(t *testing.T) {
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://www.zhichajinkong.com/dataMiddle/api/handle",
AppID: "4b78fff61ab8426f",
AppSecret: "1128f01b94124ae899c2e9f2b1f37681",
EncryptKey: "af4ca0098e6a202a5c08c413ebd9fd62",
},
logger: nil,
}
params := map[string]interface{}{
"test": "data",
}
// 创建可取消的context
ctx, cancel := context.WithCancel(context.Background())
// 立即取消
cancel()
// 应该返回context取消错误
_, err := service.CallAPI(ctx, "test_pro_id", params)
if err == nil {
t.Error("context取消后应该返回错误")
}
// 检查是否是context取消错误
if err != context.Canceled && !strings.Contains(err.Error(), "context") {
t.Errorf("期望context相关错误实际: %v", err)
}
t.Logf("Context取消错误: %v", err)
}
func TestCallAPIWithTimeout(t *testing.T) {
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://www.zhichajinkong.com/dataMiddle/api/handle",
AppID: "4b78fff61ab8426f",
AppSecret: "1128f01b94124ae899c2e9f2b1f37681",
EncryptKey: "af4ca0098e6a202a5c08c413ebd9fd62",
},
logger: nil,
}
params := map[string]interface{}{
"test": "data",
}
// 创建很短的超时
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()
// 应该因为超时而失败
_, err := service.CallAPI(ctx, "test_pro_id", params)
if err == nil {
t.Error("超时后应该返回错误")
}
// 检查是否是超时错误
if err != context.DeadlineExceeded && !strings.Contains(err.Error(), "timeout") && !strings.Contains(err.Error(), "deadline") {
t.Errorf("期望超时相关错误,实际: %v", err)
}
t.Logf("超时错误: %v", err)
}
func TestCallAPIRequestHeaders(t *testing.T) {
// 这个测试验证请求头是否正确设置
// 由于我们不能直接访问HTTP请求我们通过日志或其他方式来验证
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://www.zhichajinkong.com/dataMiddle/api/handle",
AppID: "test_app_id",
AppSecret: "test_app_secret",
EncryptKey: "test_encrypt_key",
},
logger: nil,
}
params := map[string]interface{}{
"test": "headers",
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 调用API可能会失败但我们主要测试请求头设置
_, err := service.CallAPI(ctx, "test_pro_id", params)
// 验证签名生成是否正确
timestamp := time.Now().Unix()
sign := service.generateSign(timestamp)
if sign == "" {
t.Error("签名生成失败")
}
if len(sign) != 32 {
t.Errorf("签名长度错误期望32位实际%d位", len(sign))
}
t.Logf("签名生成成功: %s", sign)
t.Logf("API调用结果: %v", err)
}
func TestZhichaErrorHandling(t *testing.T) {
// 测试核心错误类型
testCases := []struct {
name string
code string
message string
expectedErr *ZhichaError
}{
{
name: "成功状态",
code: "200",
message: "请求成功",
expectedErr: ErrSuccess,
},
{
name: "查询无记录",
code: "201",
message: "查询无记录",
expectedErr: ErrNoRecord,
},
{
name: "手机号错误",
code: "306",
message: "手机号错误",
expectedErr: ErrPhoneError,
},
{
name: "姓名错误",
code: "305",
message: "姓名错误",
expectedErr: ErrNameError,
},
{
name: "身份证号错误",
code: "307",
message: "身份证号错误",
expectedErr: ErrIDCardError,
},
{
name: "余额不足",
code: "310",
message: "余额不足",
expectedErr: ErrInsufficientBalance,
},
{
name: "用户不存在",
code: "312",
message: "用户不存在",
expectedErr: ErrUserNotExist,
},
{
name: "系统异常",
code: "500",
message: "系统异常,请联系管理员",
expectedErr: ErrSystemError,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// 测试从状态码创建错误
err := NewZhichaErrorFromCode(tc.code)
if err.Code != tc.expectedErr.Code {
t.Errorf("期望错误码 %s实际 %s", tc.expectedErr.Code, err.Code)
}
if err.Message != tc.expectedErr.Message {
t.Errorf("期望错误消息 %s实际 %s", tc.expectedErr.Message, err.Message)
}
})
}
}
func TestZhichaErrorHelpers(t *testing.T) {
// 测试错误类型判断函数
err := NewZhichaError("302", "业务参数缺失")
// 测试IsZhichaError
if !IsZhichaError(err) {
t.Error("IsZhichaError应该返回true")
}
// 测试GetZhichaError
zhichaErr := GetZhichaError(err)
if zhichaErr == nil {
t.Error("GetZhichaError应该返回非nil值")
}
if zhichaErr.Code != "302" {
t.Errorf("期望错误码302实际%s", zhichaErr.Code)
}
// 测试普通错误
normalErr := fmt.Errorf("普通错误")
if IsZhichaError(normalErr) {
t.Error("普通错误不应该被识别为智查金控错误")
}
if GetZhichaError(normalErr) != nil {
t.Error("普通错误的GetZhichaError应该返回nil")
}
}
func TestZhichaErrorString(t *testing.T) {
// 测试错误字符串格式
err := NewZhichaError("304", "请求头参数缺失")
expectedStr := "智查金控错误 [304]: 请求头参数缺失"
if err.Error() != expectedStr {
t.Errorf("期望错误字符串 %s实际 %s", expectedStr, err.Error())
}
}
func TestErrorsIsFunctionality(t *testing.T) {
// 测试 errors.Is() 功能是否正常工作
// 创建各种错误
testCases := []struct {
name string
err error
expected error
shouldMatch bool
}{
{
name: "手机号错误匹配",
err: ErrPhoneError,
expected: ErrPhoneError,
shouldMatch: true,
},
{
name: "姓名错误匹配",
err: ErrNameError,
expected: ErrNameError,
shouldMatch: true,
},
{
name: "身份证号错误匹配",
err: ErrIDCardError,
expected: ErrIDCardError,
shouldMatch: true,
},
{
name: "余额不足错误匹配",
err: ErrInsufficientBalance,
expected: ErrInsufficientBalance,
shouldMatch: true,
},
{
name: "用户不存在错误匹配",
err: ErrUserNotExist,
expected: ErrUserNotExist,
shouldMatch: true,
},
{
name: "系统错误匹配",
err: ErrSystemError,
expected: ErrSystemError,
shouldMatch: true,
},
{
name: "不同错误不匹配",
err: ErrPhoneError,
expected: ErrNameError,
shouldMatch: false,
},
{
name: "手机号错误与身份证号错误不匹配",
err: ErrPhoneError,
expected: ErrIDCardError,
shouldMatch: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// 使用 errors.Is() 进行判断
if errors.Is(tc.err, tc.expected) != tc.shouldMatch {
if tc.shouldMatch {
t.Errorf("期望 errors.Is(%v, %v) 返回 true", tc.err, tc.expected)
} else {
t.Errorf("期望 errors.Is(%v, %v) 返回 false", tc.err, tc.expected)
}
}
})
}
}
func TestErrorsIsInSwitch(t *testing.T) {
// 测试在 switch 语句中使用 errors.Is()
// 模拟API调用返回手机号错误
err := ErrPhoneError
// 使用 switch 语句进行错误判断
var result string
switch {
case errors.Is(err, ErrSuccess):
result = "请求成功"
case errors.Is(err, ErrNoRecord):
result = "查询无记录"
case errors.Is(err, ErrPhoneError):
result = "手机号格式错误"
case errors.Is(err, ErrNameError):
result = "姓名格式错误"
case errors.Is(err, ErrIDCardError):
result = "身份证号格式错误"
case errors.Is(err, ErrHeaderParamMissing):
result = "请求头参数缺失"
case errors.Is(err, ErrInsufficientBalance):
result = "余额不足"
case errors.Is(err, ErrUserNotExist):
result = "用户不存在"
case errors.Is(err, ErrUserUnauthorized):
result = "用户未授权"
case errors.Is(err, ErrSystemError):
result = "系统异常"
default:
result = "未知错误"
}
// 验证结果
expected := "手机号格式错误"
if result != expected {
t.Errorf("期望结果 %s实际 %s", expected, result)
}
t.Logf("Switch语句错误判断结果: %s", result)
}
func TestServiceEncryptDecrypt(t *testing.T) {
// 创建测试服务
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://test.com",
AppID: "test_app_id",
AppSecret: "test_app_secret",
EncryptKey: "af4ca0098e6a202a5c08c413ebd9fd62",
},
logger: nil,
}
// 测试数据
testData := "Hello, 智查金控!"
// 测试加密
encrypted, err := service.Encrypt(testData)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
if encrypted == "" {
t.Error("加密结果为空")
}
if encrypted == testData {
t.Error("加密结果与原文相同")
}
t.Logf("原文: %s", testData)
t.Logf("加密后: %s", encrypted)
// 测试解密
decrypted, err := service.Decrypt(encrypted)
if err != nil {
t.Fatalf("解密失败: %v", err)
}
if decrypted != testData {
t.Errorf("解密结果不匹配,期望: %s实际: %s", testData, decrypted)
}
t.Logf("解密后: %s", decrypted)
}
func TestEncryptWithoutKey(t *testing.T) {
// 创建没有加密密钥的服务
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://test.com",
AppID: "test_app_id",
AppSecret: "test_app_secret",
// 没有设置 EncryptKey
},
logger: nil,
}
// 应该返回错误
_, err := service.Encrypt("test data")
if err == nil {
t.Error("没有加密密钥时应该返回错误")
}
if !strings.Contains(err.Error(), "加密密钥未配置") {
t.Errorf("期望错误包含'加密密钥未配置',实际: %v", err)
}
t.Logf("预期的错误: %v", err)
}
func TestDecryptWithoutKey(t *testing.T) {
// 创建没有加密密钥的服务
service := &ZhichaService{
config: ZhichaConfig{
URL: "https://test.com",
AppID: "test_app_id",
AppSecret: "test_app_secret",
// 没有设置 EncryptKey
},
logger: nil,
}
// 应该返回错误
_, err := service.Decrypt("test encrypted data")
if err == nil {
t.Error("没有加密密钥时应该返回错误")
}
if !strings.Contains(err.Error(), "加密密钥未配置") {
t.Errorf("期望错误包含'加密密钥未配置',实际: %v", err)
}
t.Logf("预期的错误: %v", err)
}