Nenhuma descrição
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

auth_test.go 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os/exec"
  10. "testing"
  11. "time"
  12. "git.x2erp.com/qdy/go-base/model/request/queryreq"
  13. "git.x2erp.com/qdy/go-base/sdk/configure"
  14. )
  15. // TestAuthLogin 测试认证登录功能
  16. func TestAuthLogin(t *testing.T) {
  17. // 清理测试缓存
  18. cleanTestCache(t)
  19. // 获取svc-code服务地址
  20. svcCodeURL := "http://localhost:8020"
  21. // 检查svc-code服务是否运行
  22. if !isServiceRunning(t, svcCodeURL) {
  23. t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL)
  24. }
  25. // 检查配置中心服务是否运行
  26. configureURL := "http://localhost:8080"
  27. if !isServiceRunning(t, configureURL) {
  28. t.Skipf("配置中心服务未运行在 %s,跳过测试", configureURL)
  29. }
  30. // 测试1:使用SDK直接登录(验证SDK功能)
  31. t.Run("SDKLogin", func(t *testing.T) {
  32. testSDKLogin(t, configureURL)
  33. })
  34. // 测试2:通过svc-code API登录(验证集成功能)
  35. t.Run("APILogin", func(t *testing.T) {
  36. testAPILogin(t, svcCodeURL, configureURL)
  37. })
  38. // 测试3:无效凭证登录测试
  39. t.Run("InvalidCredentials", func(t *testing.T) {
  40. testInvalidCredentials(t, svcCodeURL)
  41. })
  42. }
  43. // testSDKLogin 测试直接使用SDK登录配置中心
  44. func testSDKLogin(t *testing.T, configureURL string) {
  45. // 创建SDK客户端(登录无需认证,但需要BaseURL)
  46. client, err := configure.NewBasicAuthClient(configureURL, "test", "test")
  47. if err != nil {
  48. t.Fatalf("创建SDK客户端失败: %v", err)
  49. }
  50. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  51. defer cancel()
  52. // 使用测试用户登录
  53. req := &configure.UserLoginRequest{
  54. UserID: "test-user-001",
  55. Password: "password123",
  56. }
  57. startTime := time.Now()
  58. token, err := client.LoginUser(ctx, req)
  59. elapsed := time.Since(startTime)
  60. if err != nil {
  61. if isConnectionError(err) {
  62. t.Skipf("配置中心连接失败,跳过测试: %v", err)
  63. }
  64. t.Fatalf("SDK用户登录失败: %v (耗时: %v)", err, elapsed)
  65. }
  66. if token == "" {
  67. t.Fatal("SDK用户登录返回空token")
  68. }
  69. t.Logf("SDK用户登录成功!获取Token: %s... (耗时: %v)",
  70. token[:min(50, len(token))], elapsed)
  71. // 验证token可用于创建新的认证客户端
  72. tokenClient, err := configure.NewTokenAuthClient(configureURL, token)
  73. if err != nil {
  74. t.Fatalf("使用获取的token创建客户端失败: %v", err)
  75. }
  76. t.Logf("Token客户端创建成功,BaseURL: %s", tokenClient.GetConfig().BaseURL)
  77. }
  78. // testAPILogin 测试通过svc-code API登录
  79. func testAPILogin(t *testing.T, svcCodeURL, configureURL string) {
  80. // 创建HTTP客户端
  81. httpClient := &http.Client{
  82. Timeout: 30 * time.Second,
  83. }
  84. // 准备登录请求
  85. loginURL := fmt.Sprintf("%s/api/auth/login", svcCodeURL)
  86. loginData := map[string]string{
  87. "user_id": "test-user-001",
  88. "password": "password123",
  89. }
  90. loginJSON, err := json.Marshal(loginData)
  91. if err != nil {
  92. t.Fatalf("JSON序列化失败: %v", err)
  93. }
  94. // 发送登录请求
  95. startTime := time.Now()
  96. req, err := http.NewRequest("POST", loginURL, bytes.NewReader(loginJSON))
  97. if err != nil {
  98. t.Fatalf("创建HTTP请求失败: %v", err)
  99. }
  100. req.Header.Set("Content-Type", "application/json")
  101. resp, err := httpClient.Do(req)
  102. if err != nil {
  103. t.Fatalf("发送登录请求失败: %v", err)
  104. }
  105. defer resp.Body.Close()
  106. elapsed := time.Since(startTime)
  107. // 检查响应状态码
  108. if resp.StatusCode != 200 {
  109. body, _ := io.ReadAll(resp.Body)
  110. t.Fatalf("登录API返回非200状态码: %d, 响应: %s", resp.StatusCode, string(body))
  111. }
  112. // 解析响应
  113. body, err := io.ReadAll(resp.Body)
  114. if err != nil {
  115. t.Fatalf("读取响应体失败: %v", err)
  116. }
  117. var result map[string]interface{}
  118. if err := json.Unmarshal(body, &result); err != nil {
  119. t.Fatalf("解析响应JSON失败: %v, 响应体: %s", err, string(body))
  120. }
  121. // 检查响应结构
  122. if !result["success"].(bool) {
  123. t.Fatalf("登录失败: %v", result)
  124. }
  125. token, ok := result["data"].(string)
  126. if !ok || token == "" {
  127. t.Fatalf("响应中未找到有效token: %v", result)
  128. }
  129. t.Logf("API用户登录成功!获取Token: %s... (耗时: %v)",
  130. token[:min(50, len(token))], elapsed)
  131. // 验证token可用于配置中心
  132. t.Run("ValidateTokenWithConfigure", func(t *testing.T) {
  133. validateTokenWithConfigure(t, configureURL, token)
  134. })
  135. }
  136. // testInvalidCredentials 测试无效凭证登录
  137. func testInvalidCredentials(t *testing.T, svcCodeURL string) {
  138. httpClient := &http.Client{
  139. Timeout: 10 * time.Second,
  140. }
  141. loginURL := fmt.Sprintf("%s/api/auth/login", svcCodeURL)
  142. invalidData := map[string]string{
  143. "user_id": "invalid-user",
  144. "password": "wrong-password",
  145. }
  146. invalidJSON, err := json.Marshal(invalidData)
  147. if err != nil {
  148. t.Fatalf("JSON序列化失败: %v", err)
  149. }
  150. req, err := http.NewRequest("POST", loginURL, bytes.NewReader(invalidJSON))
  151. if err != nil {
  152. t.Fatalf("创建HTTP请求失败: %v", err)
  153. }
  154. req.Header.Set("Content-Type", "application/json")
  155. resp, err := httpClient.Do(req)
  156. if err != nil {
  157. t.Fatalf("发送登录请求失败: %v", err)
  158. }
  159. defer resp.Body.Close()
  160. // 即使凭证无效,API也应该返回200(业务错误通过success字段表示)
  161. if resp.StatusCode != 200 {
  162. body, _ := io.ReadAll(resp.Body)
  163. t.Fatalf("无效凭证登录返回非200状态码: %d, 响应: %s", resp.StatusCode, string(body))
  164. }
  165. body, err := io.ReadAll(resp.Body)
  166. if err != nil {
  167. t.Fatalf("读取响应体失败: %v", err)
  168. }
  169. var result map[string]interface{}
  170. if err := json.Unmarshal(body, &result); err != nil {
  171. t.Fatalf("解析响应JSON失败: %v", err)
  172. }
  173. // 无效凭证应该返回success=false
  174. if result["success"].(bool) {
  175. t.Fatal("无效凭证登录应该失败,但成功了")
  176. }
  177. t.Logf("无效凭证登录失败(预期): %v", result)
  178. }
  179. // validateTokenWithConfigure 验证token可用于配置中心
  180. func validateTokenWithConfigure(t *testing.T, configureURL, token string) {
  181. // 使用token创建配置中心客户端
  182. client, err := configure.NewTokenAuthClient(configureURL, token)
  183. if err != nil {
  184. t.Fatalf("使用token创建配置中心客户端失败: %v", err)
  185. }
  186. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  187. defer cancel()
  188. // 尝试查询表列表(验证token权限)
  189. query := &configure.DicTableQueryRequest{
  190. QueryRequest: queryreq.QueryRequest{
  191. Page: 0,
  192. PageSize: 5,
  193. },
  194. }
  195. result, err := client.ListTables(ctx, query)
  196. if err != nil {
  197. // 如果token无效或权限不足,可能失败
  198. t.Logf("使用token查询表列表失败(可能是token权限问题): %v", err)
  199. } else {
  200. t.Logf("使用token查询表列表成功,总记录数: %d", result.TotalCount)
  201. }
  202. }
  203. // cleanTestCache 清理测试缓存
  204. func cleanTestCache(t *testing.T) {
  205. cmd := exec.Command("go", "clean", "-testcache")
  206. if err := cmd.Run(); err != nil {
  207. t.Logf("清除测试缓存失败: %v", err)
  208. }
  209. }
  210. // isServiceRunning 检查服务是否运行
  211. func isServiceRunning(t *testing.T, url string) bool {
  212. client := &http.Client{
  213. Timeout: 3 * time.Second,
  214. }
  215. // 尝试访问健康检查端点或根路径
  216. healthURL := url + "/health"
  217. resp, err := client.Get(healthURL)
  218. if err != nil {
  219. // 也尝试根路径
  220. resp, err = client.Get(url)
  221. if err != nil {
  222. return false
  223. }
  224. }
  225. defer resp.Body.Close()
  226. return resp.StatusCode == 200 || resp.StatusCode == 404
  227. }
  228. // isConnectionError 检查错误是否是连接错误
  229. func isConnectionError(err error) bool {
  230. errStr := err.Error()
  231. return contains(errStr, "connection refused") ||
  232. contains(errStr, "connect: connection refused") ||
  233. contains(errStr, "dial tcp") ||
  234. contains(errStr, "EOF") ||
  235. contains(errStr, "timeout")
  236. }
  237. // contains 检查字符串是否包含子串
  238. func contains(s, substr string) bool {
  239. return len(s) >= len(substr) && (s == substr || (len(s) > 0 && len(substr) > 0 && (s[0:len(substr)] == substr || contains(s[1:], substr))))
  240. }
  241. // min 返回两个整数的最小值
  242. func min(a, b int) int {
  243. if a < b {
  244. return a
  245. }
  246. return b
  247. }
  248. // TestHealthEndpoint 测试健康检查端点
  249. func TestHealthEndpoint(t *testing.T) {
  250. svcCodeURL := "http://localhost:8020"
  251. if !isServiceRunning(t, svcCodeURL) {
  252. t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL)
  253. }
  254. client := &http.Client{
  255. Timeout: 10 * time.Second,
  256. }
  257. resp, err := client.Get(svcCodeURL + "/api/health")
  258. if err != nil {
  259. t.Fatalf("访问健康检查端点失败: %v", err)
  260. }
  261. defer resp.Body.Close()
  262. if resp.StatusCode != 200 {
  263. t.Fatalf("健康检查端点返回非200状态码: %d", resp.StatusCode)
  264. }
  265. body, err := io.ReadAll(resp.Body)
  266. if err != nil {
  267. t.Fatalf("读取响应体失败: %v", err)
  268. }
  269. var result map[string]interface{}
  270. if err := json.Unmarshal(body, &result); err != nil {
  271. t.Fatalf("解析响应JSON失败: %v", err)
  272. }
  273. if !result["success"].(bool) {
  274. t.Fatalf("健康检查返回success=false: %v", result)
  275. }
  276. data := result["data"].(map[string]interface{})
  277. if data["status"] != "healthy" {
  278. t.Fatalf("健康检查状态不是healthy: %v", data)
  279. }
  280. t.Logf("健康检查端点测试通过: %v", data)
  281. }