Ingen beskrivning
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sdk_token_test.go 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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/configreq"
  13. "git.x2erp.com/qdy/go-base/model/request/queryreq"
  14. "git.x2erp.com/qdy/go-base/sdk/configure"
  15. )
  16. // TestSDKTokenAuth 测试SDK使用Token认证进行数据库表字典操作
  17. func TestSDKTokenAuth(t *testing.T) {
  18. // 清除测试缓存
  19. cmd := exec.Command("go", "clean", "-testcache")
  20. if err := cmd.Run(); err != nil {
  21. t.Logf("清除测试缓存失败: %v", err)
  22. // 继续执行测试
  23. }
  24. // 1. 获取用户认证token
  25. authToken, err := getUserAuthToken(t)
  26. if err != nil {
  27. t.Fatalf("获取用户认证token失败: %v", err)
  28. }
  29. t.Logf("获取到用户认证token: %s...", authToken[:50])
  30. // 2. 使用认证token创建配置token
  31. configToken, err := createConfigToken(t, authToken)
  32. if err != nil {
  33. t.Fatalf("创建配置token失败: %v", err)
  34. }
  35. t.Logf("获取到配置token: %s...", configToken[:50])
  36. // 3. 使用配置token创建SDK客户端
  37. client, err := configure.NewTokenAuthClient("http://localhost:8080", configToken)
  38. if err != nil {
  39. t.Fatalf("创建SDK客户端失败: %v", err)
  40. }
  41. t.Log("SDK客户端创建成功")
  42. // 4. 测试SDK功能
  43. testTableID := fmt.Sprintf("test_sdk_table_%d", time.Now().Unix())
  44. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  45. defer cancel()
  46. // 4.1 创建数据库表字典
  47. t.Run("CreateTableWithSDK", func(t *testing.T) {
  48. createTableWithSDK(t, ctx, client, testTableID)
  49. })
  50. // 4.2 查询数据库表字典详情
  51. t.Run("GetTableWithSDK", func(t *testing.T) {
  52. getTableWithSDK(t, ctx, client, testTableID)
  53. })
  54. // 4.3 查询数据库表字典列表
  55. t.Run("ListTablesWithSDK", func(t *testing.T) {
  56. listTablesWithSDK(t, ctx, client)
  57. })
  58. // 4.4 删除数据库表字典
  59. t.Run("DeleteTableWithSDK", func(t *testing.T) {
  60. deleteTableWithSDK(t, ctx, client, testTableID)
  61. })
  62. // 4.5 验证数据库表字典已删除
  63. t.Run("VerifyTableDeletedWithSDK", func(t *testing.T) {
  64. verifyTableDeletedWithSDK(t, ctx, client, testTableID)
  65. })
  66. t.Log("SDK Token认证测试全部通过")
  67. }
  68. // getUserAuthToken 获取用户认证token
  69. func getUserAuthToken(t *testing.T) (string, error) {
  70. httpClient := &http.Client{}
  71. // 用户登录
  72. loginURL := "http://localhost:8080/api/login/user"
  73. loginData := map[string]string{
  74. "user_id": "test-user-001",
  75. "password": "password123",
  76. }
  77. loginJSON, _ := json.Marshal(loginData)
  78. loginReq, err := http.NewRequest("POST", loginURL, bytes.NewReader(loginJSON))
  79. if err != nil {
  80. return "", fmt.Errorf("创建登录请求失败: %w", err)
  81. }
  82. loginReq.Header.Set("Content-Type", "application/json")
  83. loginResp, err := httpClient.Do(loginReq)
  84. if err != nil {
  85. return "", fmt.Errorf("发送登录请求失败: %w", err)
  86. }
  87. defer loginResp.Body.Close()
  88. loginBody, err := io.ReadAll(loginResp.Body)
  89. if err != nil {
  90. return "", fmt.Errorf("读取登录响应失败: %w", err)
  91. }
  92. var loginResult map[string]interface{}
  93. if err := json.Unmarshal(loginBody, &loginResult); err != nil {
  94. return "", fmt.Errorf("解析登录响应失败: %w", err)
  95. }
  96. if !loginResult["success"].(bool) {
  97. return "", fmt.Errorf("登录失败: %v", loginResult)
  98. }
  99. return loginResult["data"].(string), nil
  100. }
  101. // createConfigToken 创建配置token
  102. func createConfigToken(t *testing.T, authToken string) (string, error) {
  103. httpClient := &http.Client{}
  104. // 使用认证token创建配置token
  105. url := "http://localhost:8080/api/create/config/token"
  106. data := configreq.ConfigTokenRequest{
  107. ProjectID: "test-project",
  108. ServiceName: "test-service",
  109. ExpiresDays: 30,
  110. }
  111. jsonData, err := json.Marshal(data)
  112. if err != nil {
  113. return "", fmt.Errorf("JSON序列化失败: %w", err)
  114. }
  115. req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData))
  116. if err != nil {
  117. return "", fmt.Errorf("创建请求失败: %w", err)
  118. }
  119. // Token Auth 认证
  120. req.Header.Set("Authorization", "Bearer "+authToken)
  121. req.Header.Set("Content-Type", "application/json")
  122. resp, err := httpClient.Do(req)
  123. if err != nil {
  124. return "", fmt.Errorf("发送请求失败: %w", err)
  125. }
  126. defer resp.Body.Close()
  127. body, err := io.ReadAll(resp.Body)
  128. if err != nil {
  129. return "", fmt.Errorf("读取响应失败: %w", err)
  130. }
  131. var result map[string]interface{}
  132. if err := json.Unmarshal(body, &result); err != nil {
  133. return "", fmt.Errorf("解析响应失败: %w", err)
  134. }
  135. if resp.StatusCode != 200 {
  136. return "", fmt.Errorf("期望状态码200,实际: %d", resp.StatusCode)
  137. }
  138. if !result["success"].(bool) {
  139. return "", fmt.Errorf("创建配置token失败: %v", result)
  140. }
  141. if data, ok := result["data"].(string); !ok || data == "" {
  142. return "", fmt.Errorf("未返回有效的配置token")
  143. } else {
  144. return data, nil
  145. }
  146. }
  147. // createTableWithSDK 使用SDK创建数据库表字典
  148. func createTableWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) {
  149. req := &configure.DicTableRequest{
  150. TableID: tableID,
  151. TableType: "实体表",
  152. Name: "SDK测试表",
  153. Description: "使用SDK Token认证创建的测试表",
  154. Fields: []configure.DicTableFieldRequest{
  155. {
  156. FieldID: tableID + ".id",
  157. TableID: tableID,
  158. FiledType: "实际字段",
  159. DataType: "数值型",
  160. FieldName: "id",
  161. FieldNameCN: "主键ID",
  162. Description: "主键字段",
  163. },
  164. {
  165. FieldID: tableID + ".name",
  166. TableID: tableID,
  167. FiledType: "实际字段",
  168. DataType: "字符型",
  169. FieldName: "name",
  170. FieldNameCN: "名称",
  171. Description: "名称字段",
  172. },
  173. {
  174. FieldID: tableID + ".created_at",
  175. TableID: tableID,
  176. FiledType: "实际字段",
  177. DataType: "日期型",
  178. FieldName: "created_at",
  179. FieldNameCN: "创建时间",
  180. Description: "创建时间字段",
  181. },
  182. },
  183. }
  184. detail, err := client.SaveTable(ctx, req)
  185. if err != nil {
  186. t.Fatalf("使用SDK创建表失败: %v", err)
  187. }
  188. if detail.Table.TableID != tableID {
  189. t.Errorf("期望表ID: %s, 实际: %s", tableID, detail.Table.TableID)
  190. }
  191. if len(detail.Fields) != 3 {
  192. t.Errorf("期望字段数: 3, 实际: %d", len(detail.Fields))
  193. }
  194. t.Logf("使用SDK创建表成功: %s, 包含 %d 个字段", tableID, len(detail.Fields))
  195. }
  196. // getTableWithSDK 使用SDK查询数据库表字典详情
  197. func getTableWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) {
  198. detail, err := client.GetTable(ctx, tableID)
  199. if err != nil {
  200. t.Fatalf("使用SDK查询表详情失败: %v", err)
  201. }
  202. if detail.Table.TableID != tableID {
  203. t.Errorf("期望表ID: %s, 实际: %s", tableID, detail.Table.TableID)
  204. }
  205. if detail.Table.Name != "SDK测试表" {
  206. t.Errorf("期望表名称: SDK测试表, 实际: %s", detail.Table.Name)
  207. }
  208. if len(detail.Fields) != 3 {
  209. t.Errorf("期望字段数: 3, 实际: %d", len(detail.Fields))
  210. }
  211. t.Logf("使用SDK查询表详情成功: %s, 表名称: %s", tableID, detail.Table.Name)
  212. }
  213. // listTablesWithSDK 使用SDK查询数据库表字典列表
  214. func listTablesWithSDK(t *testing.T, ctx context.Context, client *configure.Client) {
  215. query := &configure.DicTableQueryRequest{
  216. QueryRequest: queryreq.QueryRequest{
  217. Page: 0,
  218. PageSize: 10,
  219. },
  220. }
  221. result, err := client.ListTables(ctx, query)
  222. if err != nil {
  223. t.Fatalf("使用SDK查询表列表失败: %v", err)
  224. }
  225. if result.TotalCount < 0 {
  226. t.Errorf("总记录数应该 >= 0, 实际: %d", result.TotalCount)
  227. }
  228. t.Logf("使用SDK查询表列表成功: 总记录数 %d, 最后页 %d", result.TotalCount, result.LastPage)
  229. }
  230. // deleteTableWithSDK 使用SDK删除数据库表字典
  231. func deleteTableWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) {
  232. err := client.DeleteTable(ctx, tableID)
  233. if err != nil {
  234. t.Fatalf("使用SDK删除表失败: %v", err)
  235. }
  236. t.Logf("使用SDK删除表成功: %s", tableID)
  237. }
  238. // verifyTableDeletedWithSDK 使用SDK验证数据库表字典已删除
  239. func verifyTableDeletedWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) {
  240. _, err := client.GetTable(ctx, tableID)
  241. if err == nil {
  242. t.Errorf("期望表 %s 已被删除,但查询成功", tableID)
  243. } else {
  244. // 检查错误是否为"未找到"
  245. if err.Error() != "not found" && !contains(err.Error(), "不存在") {
  246. t.Logf("表删除验证: %v", err)
  247. }
  248. t.Logf("表 %s 已成功删除(查询返回错误)", tableID)
  249. }
  250. }
  251. // contains 检查字符串是否包含子串
  252. func contains(s, substr string) bool {
  253. return len(s) >= len(substr) && (s == substr || (len(s) > 0 && len(substr) > 0 && (s[0:len(substr)] == substr || contains(s[1:], substr))))
  254. }
  255. // TestBatchSaveWithSDK 测试SDK批量保存数据库表字典
  256. func TestBatchSaveWithSDK(t *testing.T) {
  257. // 1. 获取用户认证token
  258. authToken, err := getUserAuthToken(t)
  259. if err != nil {
  260. t.Fatalf("获取用户认证token失败: %v", err)
  261. }
  262. t.Logf("获取到用户认证token: %s...", authToken[:50])
  263. // 2. 使用认证token创建配置token
  264. configToken, err := createConfigToken(t, authToken)
  265. if err != nil {
  266. t.Fatalf("创建配置token失败: %v", err)
  267. }
  268. t.Logf("获取到配置token: %s...", configToken[:50])
  269. // 3. 使用配置token创建SDK客户端
  270. client, err := configure.NewTokenAuthClient("http://localhost:8080", configToken)
  271. if err != nil {
  272. t.Fatalf("创建SDK客户端失败: %v", err)
  273. }
  274. t.Log("SDK客户端创建成功")
  275. // 4. 准备批量保存测试数据
  276. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  277. defer cancel()
  278. // 生成唯一的表ID避免冲突
  279. timestamp := time.Now().Unix()
  280. table1ID := fmt.Sprintf("sdk_batch_table_001_%d", timestamp)
  281. table2ID := fmt.Sprintf("sdk_batch_table_002_%d", timestamp)
  282. // 创建批量保存请求
  283. req := &configure.BatchSaveDicTablesRequest{
  284. Tables: []configure.DicTableRequest{
  285. {
  286. TableID: table1ID,
  287. TableType: "实体表",
  288. Name: "SDK批量测试表001",
  289. Description: "SDK批量测试表001描述",
  290. },
  291. {
  292. TableID: table2ID,
  293. TableType: "视图",
  294. Name: "SDK批量测试表002",
  295. Description: "SDK批量测试表002描述",
  296. },
  297. },
  298. Fields: []configure.DicTableFieldRequest{
  299. // 表1的字段
  300. {
  301. FieldID: table1ID + ".id",
  302. TableID: table1ID,
  303. FiledType: "实际字段",
  304. DataType: "数值型",
  305. FieldName: "id",
  306. FieldNameCN: "主键ID",
  307. Description: "表1主键字段",
  308. },
  309. {
  310. FieldID: table1ID + ".name",
  311. TableID: table1ID,
  312. FiledType: "实际字段",
  313. DataType: "字符型",
  314. FieldName: "name",
  315. FieldNameCN: "名称",
  316. Description: "表1名称字段",
  317. },
  318. // 表2的字段
  319. {
  320. FieldID: table2ID + ".code",
  321. TableID: table2ID,
  322. FiledType: "实际字段",
  323. DataType: "字符型",
  324. FieldName: "code",
  325. FieldNameCN: "编码",
  326. Description: "表2编码字段",
  327. },
  328. {
  329. FieldID: table2ID + ".value",
  330. TableID: table2ID,
  331. FiledType: "计算字段",
  332. DataType: "数值型",
  333. FieldName: "value",
  334. FieldNameCN: "数值",
  335. Description: "表2数值字段",
  336. },
  337. },
  338. }
  339. // 5. 执行批量保存
  340. t.Run("BatchSaveTables", func(t *testing.T) {
  341. startTime := time.Now()
  342. if err := client.BatchSaveTables(ctx, req); err != nil {
  343. t.Fatalf("批量保存失败: %v", err)
  344. }
  345. elapsed := time.Since(startTime)
  346. t.Logf("批量保存成功!耗时: %v", elapsed)
  347. t.Logf("保存了 %d 个表,%d 个字段", len(req.Tables), len(req.Fields))
  348. })
  349. // 6. 验证批量保存结果
  350. t.Run("VerifyBatchSavedTables", func(t *testing.T) {
  351. // 验证表1
  352. detail1, err := client.GetTable(ctx, table1ID)
  353. if err != nil {
  354. t.Errorf("查询表1详情失败: %v", err)
  355. } else {
  356. if detail1.Table.TableID != table1ID {
  357. t.Errorf("表1 ID不匹配: 期望 %s, 实际 %s", table1ID, detail1.Table.TableID)
  358. }
  359. if detail1.Table.TableType != "实体表" {
  360. t.Errorf("表1 类型不匹配: 期望 实体表, 实际 %s", detail1.Table.TableType)
  361. }
  362. if len(detail1.Fields) != 2 {
  363. t.Errorf("表1 字段数不匹配: 期望 2, 实际 %d", len(detail1.Fields))
  364. }
  365. t.Logf("表1验证成功: %s, 包含 %d 个字段", table1ID, len(detail1.Fields))
  366. }
  367. // 验证表2
  368. detail2, err := client.GetTable(ctx, table2ID)
  369. if err != nil {
  370. t.Errorf("查询表2详情失败: %v", err)
  371. } else {
  372. if detail2.Table.TableID != table2ID {
  373. t.Errorf("表2 ID不匹配: 期望 %s, 实际 %s", table2ID, detail2.Table.TableID)
  374. }
  375. if detail2.Table.TableType != "视图" {
  376. t.Errorf("表2 类型不匹配: 期望 视图, 实际 %s", detail2.Table.TableType)
  377. }
  378. if len(detail2.Fields) != 2 {
  379. t.Errorf("表2 字段数不匹配: 期望 2, 实际 %d", len(detail2.Fields))
  380. }
  381. t.Logf("表2验证成功: %s, 包含 %d 个字段", table2ID, len(detail2.Fields))
  382. }
  383. })
  384. // 7. 清理测试数据
  385. t.Run("CleanupBatchTestData", func(t *testing.T) {
  386. // 删除表1
  387. if err := client.DeleteTable(ctx, table1ID); err != nil {
  388. t.Errorf("删除表1失败: %v", err)
  389. } else {
  390. t.Logf("表1删除成功: %s", table1ID)
  391. }
  392. // 删除表2
  393. if err := client.DeleteTable(ctx, table2ID); err != nil {
  394. t.Errorf("删除表2失败: %v", err)
  395. } else {
  396. t.Logf("表2删除成功: %s", table2ID)
  397. }
  398. })
  399. t.Log("SDK批量保存测试全部完成")
  400. }
  401. // TestSDKUserLogin 测试SDK用户登录功能
  402. func TestSDKUserLogin(t *testing.T) {
  403. // 创建客户端(登录端点无需认证,但需要BaseURL)
  404. // 使用任意认证凭证,因为登录时skipAuth=true
  405. client, err := configure.NewBasicAuthClient("http://localhost:8080", "test", "test")
  406. if err != nil {
  407. t.Fatalf("创建SDK客户端失败: %v", err)
  408. }
  409. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  410. defer cancel()
  411. // 测试登录功能
  412. t.Run("UserLoginWithSDK", func(t *testing.T) {
  413. // 尝试使用测试用户登录
  414. req := &configure.UserLoginRequest{
  415. UserID: "test-user-001",
  416. Password: "password123",
  417. }
  418. startTime := time.Now()
  419. token, err := client.LoginUser(ctx, req)
  420. elapsed := time.Since(startTime)
  421. if err != nil {
  422. // 检查是否是连接错误(服务未启动)
  423. if isConnectionError(err) {
  424. t.Skipf("配置中心服务未启动,跳过测试: %v", err)
  425. }
  426. t.Fatalf("SDK用户登录失败: %v (耗时: %v)", err, elapsed)
  427. }
  428. if token == "" {
  429. t.Fatal("SDK用户登录返回空token")
  430. }
  431. t.Logf("SDK用户登录成功!获取Token: %s... (耗时: %v)", token[:min(50, len(token))], elapsed)
  432. // 验证token可用于创建新的认证客户端
  433. t.Run("CreateTokenClient", func(t *testing.T) {
  434. tokenClient, err := configure.NewTokenAuthClient("http://localhost:8080", token)
  435. if err != nil {
  436. t.Fatalf("使用获取的token创建客户端失败: %v", err)
  437. }
  438. t.Logf("Token客户端创建成功,BaseURL: %s", tokenClient.GetConfig().BaseURL)
  439. // 测试token客户端是否可正常工作(查询表列表)
  440. query := &configure.DicTableQueryRequest{
  441. QueryRequest: queryreq.QueryRequest{
  442. Page: 0,
  443. PageSize: 5,
  444. },
  445. }
  446. result, err := tokenClient.ListTables(ctx, query)
  447. if err != nil {
  448. // 如果token无效或权限不足,可能失败
  449. t.Logf("使用token查询表列表失败(可能是token权限问题): %v", err)
  450. } else {
  451. t.Logf("使用token查询表列表成功,总记录数: %d", result.TotalCount)
  452. }
  453. })
  454. })
  455. // 测试无效凭证登录
  456. t.Run("InvalidCredentialsLogin", func(t *testing.T) {
  457. req := &configure.UserLoginRequest{
  458. UserID: "invalid-user",
  459. Password: "wrong-password",
  460. }
  461. _, err := client.LoginUser(ctx, req)
  462. if err == nil {
  463. t.Fatal("使用无效凭证登录应该失败,但成功了")
  464. }
  465. // 检查错误类型
  466. t.Logf("无效凭证登录失败(预期): %v", err)
  467. })
  468. }
  469. // isConnectionError 检查错误是否是连接错误(服务未启动)
  470. func isConnectionError(err error) bool {
  471. errStr := err.Error()
  472. return contains(errStr, "connection refused") ||
  473. contains(errStr, "connect: connection refused") ||
  474. contains(errStr, "dial tcp") ||
  475. contains(errStr, "EOF") ||
  476. contains(errStr, "timeout")
  477. }
  478. // min 返回两个整数的最小值
  479. func min(a, b int) int {
  480. if a < b {
  481. return a
  482. }
  483. return b
  484. }