package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os/exec" "testing" "time" "git.x2erp.com/qdy/go-base/model/request/queryreq" "git.x2erp.com/qdy/go-base/sdk/configure" ) // TestAuthLogin 测试认证登录功能 func TestAuthLogin(t *testing.T) { // 清理测试缓存 cleanTestCache(t) // 获取svc-code服务地址 svcCodeURL := "http://localhost:8020" // 检查svc-code服务是否运行 if !isServiceRunning(t, svcCodeURL) { t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL) } // 检查配置中心服务是否运行 configureURL := "http://localhost:8080" if !isServiceRunning(t, configureURL) { t.Skipf("配置中心服务未运行在 %s,跳过测试", configureURL) } // 测试1:使用SDK直接登录(验证SDK功能) t.Run("SDKLogin", func(t *testing.T) { testSDKLogin(t, configureURL) }) // 测试2:通过svc-code API登录(验证集成功能) t.Run("APILogin", func(t *testing.T) { testAPILogin(t, svcCodeURL, configureURL) }) // 测试3:无效凭证登录测试 t.Run("InvalidCredentials", func(t *testing.T) { testInvalidCredentials(t, svcCodeURL) }) } // testSDKLogin 测试直接使用SDK登录配置中心 func testSDKLogin(t *testing.T, configureURL string) { // 创建SDK客户端(登录无需认证,但需要BaseURL) client, err := configure.NewBasicAuthClient(configureURL, "test", "test") if err != nil { t.Fatalf("创建SDK客户端失败: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // 使用测试用户登录 req := &configure.UserLoginRequest{ UserID: "test-user-001", Password: "password123", } startTime := time.Now() token, err := client.LoginUser(ctx, req) elapsed := time.Since(startTime) if err != nil { if isConnectionError(err) { t.Skipf("配置中心连接失败,跳过测试: %v", err) } t.Fatalf("SDK用户登录失败: %v (耗时: %v)", err, elapsed) } if token == "" { t.Fatal("SDK用户登录返回空token") } t.Logf("SDK用户登录成功!获取Token: %s... (耗时: %v)", token[:min(50, len(token))], elapsed) // 验证token可用于创建新的认证客户端 tokenClient, err := configure.NewTokenAuthClient(configureURL, token) if err != nil { t.Fatalf("使用获取的token创建客户端失败: %v", err) } t.Logf("Token客户端创建成功,BaseURL: %s", tokenClient.GetConfig().BaseURL) } // testAPILogin 测试通过svc-code API登录 func testAPILogin(t *testing.T, svcCodeURL, configureURL string) { // 创建HTTP客户端 httpClient := &http.Client{ Timeout: 30 * time.Second, } // 准备登录请求 loginURL := fmt.Sprintf("%s/api/auth/login", svcCodeURL) loginData := map[string]string{ "user_id": "test-user-001", "password": "password123", } loginJSON, err := json.Marshal(loginData) if err != nil { t.Fatalf("JSON序列化失败: %v", err) } // 发送登录请求 startTime := time.Now() req, err := http.NewRequest("POST", loginURL, bytes.NewReader(loginJSON)) if err != nil { t.Fatalf("创建HTTP请求失败: %v", err) } req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) if err != nil { t.Fatalf("发送登录请求失败: %v", err) } defer resp.Body.Close() elapsed := time.Since(startTime) // 检查响应状态码 if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) t.Fatalf("登录API返回非200状态码: %d, 响应: %s", resp.StatusCode, string(body)) } // 解析响应 body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("读取响应体失败: %v", err) } var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { t.Fatalf("解析响应JSON失败: %v, 响应体: %s", err, string(body)) } // 检查响应结构 if !result["success"].(bool) { t.Fatalf("登录失败: %v", result) } token, ok := result["data"].(string) if !ok || token == "" { t.Fatalf("响应中未找到有效token: %v", result) } t.Logf("API用户登录成功!获取Token: %s... (耗时: %v)", token[:min(50, len(token))], elapsed) // 验证token可用于配置中心 t.Run("ValidateTokenWithConfigure", func(t *testing.T) { validateTokenWithConfigure(t, configureURL, token) }) } // testInvalidCredentials 测试无效凭证登录 func testInvalidCredentials(t *testing.T, svcCodeURL string) { httpClient := &http.Client{ Timeout: 10 * time.Second, } loginURL := fmt.Sprintf("%s/api/auth/login", svcCodeURL) invalidData := map[string]string{ "user_id": "invalid-user", "password": "wrong-password", } invalidJSON, err := json.Marshal(invalidData) if err != nil { t.Fatalf("JSON序列化失败: %v", err) } req, err := http.NewRequest("POST", loginURL, bytes.NewReader(invalidJSON)) if err != nil { t.Fatalf("创建HTTP请求失败: %v", err) } req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) if err != nil { t.Fatalf("发送登录请求失败: %v", err) } defer resp.Body.Close() // 即使凭证无效,API也应该返回200(业务错误通过success字段表示) if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) t.Fatalf("无效凭证登录返回非200状态码: %d, 响应: %s", resp.StatusCode, string(body)) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("读取响应体失败: %v", err) } var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { t.Fatalf("解析响应JSON失败: %v", err) } // 无效凭证应该返回success=false if result["success"].(bool) { t.Fatal("无效凭证登录应该失败,但成功了") } t.Logf("无效凭证登录失败(预期): %v", result) } // validateTokenWithConfigure 验证token可用于配置中心 func validateTokenWithConfigure(t *testing.T, configureURL, token string) { // 使用token创建配置中心客户端 client, err := configure.NewTokenAuthClient(configureURL, token) if err != nil { t.Fatalf("使用token创建配置中心客户端失败: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // 尝试查询表列表(验证token权限) query := &configure.DicTableQueryRequest{ QueryRequest: queryreq.QueryRequest{ Page: 0, PageSize: 5, }, } result, err := client.ListTables(ctx, query) if err != nil { // 如果token无效或权限不足,可能失败 t.Logf("使用token查询表列表失败(可能是token权限问题): %v", err) } else { t.Logf("使用token查询表列表成功,总记录数: %d", result.TotalCount) } } // cleanTestCache 清理测试缓存 func cleanTestCache(t *testing.T) { cmd := exec.Command("go", "clean", "-testcache") if err := cmd.Run(); err != nil { t.Logf("清除测试缓存失败: %v", err) } } // isServiceRunning 检查服务是否运行 func isServiceRunning(t *testing.T, url string) bool { client := &http.Client{ Timeout: 3 * time.Second, } // 尝试访问健康检查端点或根路径 healthURL := url + "/health" resp, err := client.Get(healthURL) if err != nil { // 也尝试根路径 resp, err = client.Get(url) if err != nil { return false } } defer resp.Body.Close() return resp.StatusCode == 200 || resp.StatusCode == 404 } // isConnectionError 检查错误是否是连接错误 func isConnectionError(err error) bool { errStr := err.Error() return contains(errStr, "connection refused") || contains(errStr, "connect: connection refused") || contains(errStr, "dial tcp") || contains(errStr, "EOF") || contains(errStr, "timeout") } // contains 检查字符串是否包含子串 func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || (len(s) > 0 && len(substr) > 0 && (s[0:len(substr)] == substr || contains(s[1:], substr)))) } // min 返回两个整数的最小值 func min(a, b int) int { if a < b { return a } return b } // TestHealthEndpoint 测试健康检查端点 func TestHealthEndpoint(t *testing.T) { svcCodeURL := "http://localhost:8020" if !isServiceRunning(t, svcCodeURL) { t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL) } client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(svcCodeURL + "/api/health") if err != nil { t.Fatalf("访问健康检查端点失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("健康检查端点返回非200状态码: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("读取响应体失败: %v", err) } var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { t.Fatalf("解析响应JSON失败: %v", err) } if !result["success"].(bool) { t.Fatalf("健康检查返回success=false: %v", result) } data := result["data"].(map[string]interface{}) if data["status"] != "healthy" { t.Fatalf("健康检查状态不是healthy: %v", data) } t.Logf("健康检查端点测试通过: %v", data) }