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) }