package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os/exec" "testing" "time" "git.x2erp.com/qdy/go-base/model/request/configreq" "git.x2erp.com/qdy/go-base/model/request/queryreq" "git.x2erp.com/qdy/go-base/sdk/configure" ) // TestSDKTokenAuth 测试SDK使用Token认证进行数据库表字典操作 func TestSDKTokenAuth(t *testing.T) { // 清除测试缓存 cmd := exec.Command("go", "clean", "-testcache") if err := cmd.Run(); err != nil { t.Logf("清除测试缓存失败: %v", err) // 继续执行测试 } // 1. 获取用户认证token authToken, err := getUserAuthToken(t) if err != nil { t.Fatalf("获取用户认证token失败: %v", err) } t.Logf("获取到用户认证token: %s...", authToken[:50]) // 2. 使用认证token创建配置token configToken, err := createConfigToken(t, authToken) if err != nil { t.Fatalf("创建配置token失败: %v", err) } t.Logf("获取到配置token: %s...", configToken[:50]) // 3. 使用配置token创建SDK客户端 client, err := configure.NewTokenAuthClient("http://localhost:8080", configToken) if err != nil { t.Fatalf("创建SDK客户端失败: %v", err) } t.Log("SDK客户端创建成功") // 4. 测试SDK功能 testTableID := fmt.Sprintf("test_sdk_table_%d", time.Now().Unix()) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // 4.1 创建数据库表字典 t.Run("CreateTableWithSDK", func(t *testing.T) { createTableWithSDK(t, ctx, client, testTableID) }) // 4.2 查询数据库表字典详情 t.Run("GetTableWithSDK", func(t *testing.T) { getTableWithSDK(t, ctx, client, testTableID) }) // 4.3 查询数据库表字典列表 t.Run("ListTablesWithSDK", func(t *testing.T) { listTablesWithSDK(t, ctx, client) }) // 4.4 删除数据库表字典 t.Run("DeleteTableWithSDK", func(t *testing.T) { deleteTableWithSDK(t, ctx, client, testTableID) }) // 4.5 验证数据库表字典已删除 t.Run("VerifyTableDeletedWithSDK", func(t *testing.T) { verifyTableDeletedWithSDK(t, ctx, client, testTableID) }) t.Log("SDK Token认证测试全部通过") } // getUserAuthToken 获取用户认证token func getUserAuthToken(t *testing.T) (string, error) { httpClient := &http.Client{} // 用户登录 loginURL := "http://localhost:8080/api/login/user" loginData := map[string]string{ "user_id": "test-user-001", "password": "password123", } loginJSON, _ := json.Marshal(loginData) loginReq, err := http.NewRequest("POST", loginURL, bytes.NewReader(loginJSON)) if err != nil { return "", fmt.Errorf("创建登录请求失败: %w", err) } loginReq.Header.Set("Content-Type", "application/json") loginResp, err := httpClient.Do(loginReq) if err != nil { return "", fmt.Errorf("发送登录请求失败: %w", err) } defer loginResp.Body.Close() loginBody, err := io.ReadAll(loginResp.Body) if err != nil { return "", fmt.Errorf("读取登录响应失败: %w", err) } var loginResult map[string]interface{} if err := json.Unmarshal(loginBody, &loginResult); err != nil { return "", fmt.Errorf("解析登录响应失败: %w", err) } if !loginResult["success"].(bool) { return "", fmt.Errorf("登录失败: %v", loginResult) } return loginResult["data"].(string), nil } // createConfigToken 创建配置token func createConfigToken(t *testing.T, authToken string) (string, error) { httpClient := &http.Client{} // 使用认证token创建配置token url := "http://localhost:8080/api/create/config/token" data := configreq.ConfigTokenRequest{ ProjectID: "test-project", ServiceName: "test-service", ExpiresDays: 30, } jsonData, err := json.Marshal(data) if err != nil { return "", fmt.Errorf("JSON序列化失败: %w", err) } req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData)) if err != nil { return "", fmt.Errorf("创建请求失败: %w", err) } // Token Auth 认证 req.Header.Set("Authorization", "Bearer "+authToken) req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) if err != nil { return "", fmt.Errorf("发送请求失败: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("读取响应失败: %w", err) } var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { return "", fmt.Errorf("解析响应失败: %w", err) } if resp.StatusCode != 200 { return "", fmt.Errorf("期望状态码200,实际: %d", resp.StatusCode) } if !result["success"].(bool) { return "", fmt.Errorf("创建配置token失败: %v", result) } if data, ok := result["data"].(string); !ok || data == "" { return "", fmt.Errorf("未返回有效的配置token") } else { return data, nil } } // createTableWithSDK 使用SDK创建数据库表字典 func createTableWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) { req := &configure.DicTableRequest{ TableID: tableID, TableType: "实体表", Name: "SDK测试表", Description: "使用SDK Token认证创建的测试表", Fields: []configure.DicTableFieldRequest{ { FieldID: tableID + ".id", TableID: tableID, FiledType: "实际字段", DataType: "数值型", FieldName: "id", FieldNameCN: "主键ID", Description: "主键字段", }, { FieldID: tableID + ".name", TableID: tableID, FiledType: "实际字段", DataType: "字符型", FieldName: "name", FieldNameCN: "名称", Description: "名称字段", }, { FieldID: tableID + ".created_at", TableID: tableID, FiledType: "实际字段", DataType: "日期型", FieldName: "created_at", FieldNameCN: "创建时间", Description: "创建时间字段", }, }, } detail, err := client.SaveTable(ctx, req) if err != nil { t.Fatalf("使用SDK创建表失败: %v", err) } if detail.Table.TableID != tableID { t.Errorf("期望表ID: %s, 实际: %s", tableID, detail.Table.TableID) } if len(detail.Fields) != 3 { t.Errorf("期望字段数: 3, 实际: %d", len(detail.Fields)) } t.Logf("使用SDK创建表成功: %s, 包含 %d 个字段", tableID, len(detail.Fields)) } // getTableWithSDK 使用SDK查询数据库表字典详情 func getTableWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) { detail, err := client.GetTable(ctx, tableID) if err != nil { t.Fatalf("使用SDK查询表详情失败: %v", err) } if detail.Table.TableID != tableID { t.Errorf("期望表ID: %s, 实际: %s", tableID, detail.Table.TableID) } if detail.Table.Name != "SDK测试表" { t.Errorf("期望表名称: SDK测试表, 实际: %s", detail.Table.Name) } if len(detail.Fields) != 3 { t.Errorf("期望字段数: 3, 实际: %d", len(detail.Fields)) } t.Logf("使用SDK查询表详情成功: %s, 表名称: %s", tableID, detail.Table.Name) } // listTablesWithSDK 使用SDK查询数据库表字典列表 func listTablesWithSDK(t *testing.T, ctx context.Context, client *configure.Client) { query := &configure.DicTableQueryRequest{ QueryRequest: queryreq.QueryRequest{ Page: 0, PageSize: 10, }, } result, err := client.ListTables(ctx, query) if err != nil { t.Fatalf("使用SDK查询表列表失败: %v", err) } if result.TotalCount < 0 { t.Errorf("总记录数应该 >= 0, 实际: %d", result.TotalCount) } t.Logf("使用SDK查询表列表成功: 总记录数 %d, 最后页 %d", result.TotalCount, result.LastPage) } // deleteTableWithSDK 使用SDK删除数据库表字典 func deleteTableWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) { err := client.DeleteTable(ctx, tableID) if err != nil { t.Fatalf("使用SDK删除表失败: %v", err) } t.Logf("使用SDK删除表成功: %s", tableID) } // verifyTableDeletedWithSDK 使用SDK验证数据库表字典已删除 func verifyTableDeletedWithSDK(t *testing.T, ctx context.Context, client *configure.Client, tableID string) { _, err := client.GetTable(ctx, tableID) if err == nil { t.Errorf("期望表 %s 已被删除,但查询成功", tableID) } else { // 检查错误是否为"未找到" if err.Error() != "not found" && !contains(err.Error(), "不存在") { t.Logf("表删除验证: %v", err) } t.Logf("表 %s 已成功删除(查询返回错误)", tableID) } } // 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)))) } // TestBatchSaveWithSDK 测试SDK批量保存数据库表字典 func TestBatchSaveWithSDK(t *testing.T) { // 1. 获取用户认证token authToken, err := getUserAuthToken(t) if err != nil { t.Fatalf("获取用户认证token失败: %v", err) } t.Logf("获取到用户认证token: %s...", authToken[:50]) // 2. 使用认证token创建配置token configToken, err := createConfigToken(t, authToken) if err != nil { t.Fatalf("创建配置token失败: %v", err) } t.Logf("获取到配置token: %s...", configToken[:50]) // 3. 使用配置token创建SDK客户端 client, err := configure.NewTokenAuthClient("http://localhost:8080", configToken) if err != nil { t.Fatalf("创建SDK客户端失败: %v", err) } t.Log("SDK客户端创建成功") // 4. 准备批量保存测试数据 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // 生成唯一的表ID避免冲突 timestamp := time.Now().Unix() table1ID := fmt.Sprintf("sdk_batch_table_001_%d", timestamp) table2ID := fmt.Sprintf("sdk_batch_table_002_%d", timestamp) // 创建批量保存请求 req := &configure.BatchSaveDicTablesRequest{ Tables: []configure.DicTableRequest{ { TableID: table1ID, TableType: "实体表", Name: "SDK批量测试表001", Description: "SDK批量测试表001描述", }, { TableID: table2ID, TableType: "视图", Name: "SDK批量测试表002", Description: "SDK批量测试表002描述", }, }, Fields: []configure.DicTableFieldRequest{ // 表1的字段 { FieldID: table1ID + ".id", TableID: table1ID, FiledType: "实际字段", DataType: "数值型", FieldName: "id", FieldNameCN: "主键ID", Description: "表1主键字段", }, { FieldID: table1ID + ".name", TableID: table1ID, FiledType: "实际字段", DataType: "字符型", FieldName: "name", FieldNameCN: "名称", Description: "表1名称字段", }, // 表2的字段 { FieldID: table2ID + ".code", TableID: table2ID, FiledType: "实际字段", DataType: "字符型", FieldName: "code", FieldNameCN: "编码", Description: "表2编码字段", }, { FieldID: table2ID + ".value", TableID: table2ID, FiledType: "计算字段", DataType: "数值型", FieldName: "value", FieldNameCN: "数值", Description: "表2数值字段", }, }, } // 5. 执行批量保存 t.Run("BatchSaveTables", func(t *testing.T) { startTime := time.Now() if err := client.BatchSaveTables(ctx, req); err != nil { t.Fatalf("批量保存失败: %v", err) } elapsed := time.Since(startTime) t.Logf("批量保存成功!耗时: %v", elapsed) t.Logf("保存了 %d 个表,%d 个字段", len(req.Tables), len(req.Fields)) }) // 6. 验证批量保存结果 t.Run("VerifyBatchSavedTables", func(t *testing.T) { // 验证表1 detail1, err := client.GetTable(ctx, table1ID) if err != nil { t.Errorf("查询表1详情失败: %v", err) } else { if detail1.Table.TableID != table1ID { t.Errorf("表1 ID不匹配: 期望 %s, 实际 %s", table1ID, detail1.Table.TableID) } if detail1.Table.TableType != "实体表" { t.Errorf("表1 类型不匹配: 期望 实体表, 实际 %s", detail1.Table.TableType) } if len(detail1.Fields) != 2 { t.Errorf("表1 字段数不匹配: 期望 2, 实际 %d", len(detail1.Fields)) } t.Logf("表1验证成功: %s, 包含 %d 个字段", table1ID, len(detail1.Fields)) } // 验证表2 detail2, err := client.GetTable(ctx, table2ID) if err != nil { t.Errorf("查询表2详情失败: %v", err) } else { if detail2.Table.TableID != table2ID { t.Errorf("表2 ID不匹配: 期望 %s, 实际 %s", table2ID, detail2.Table.TableID) } if detail2.Table.TableType != "视图" { t.Errorf("表2 类型不匹配: 期望 视图, 实际 %s", detail2.Table.TableType) } if len(detail2.Fields) != 2 { t.Errorf("表2 字段数不匹配: 期望 2, 实际 %d", len(detail2.Fields)) } t.Logf("表2验证成功: %s, 包含 %d 个字段", table2ID, len(detail2.Fields)) } }) // 7. 清理测试数据 t.Run("CleanupBatchTestData", func(t *testing.T) { // 删除表1 if err := client.DeleteTable(ctx, table1ID); err != nil { t.Errorf("删除表1失败: %v", err) } else { t.Logf("表1删除成功: %s", table1ID) } // 删除表2 if err := client.DeleteTable(ctx, table2ID); err != nil { t.Errorf("删除表2失败: %v", err) } else { t.Logf("表2删除成功: %s", table2ID) } }) t.Log("SDK批量保存测试全部完成") } // TestSDKUserLogin 测试SDK用户登录功能 func TestSDKUserLogin(t *testing.T) { // 创建客户端(登录端点无需认证,但需要BaseURL) // 使用任意认证凭证,因为登录时skipAuth=true client, err := configure.NewBasicAuthClient("http://localhost:8080", "test", "test") if err != nil { t.Fatalf("创建SDK客户端失败: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // 测试登录功能 t.Run("UserLoginWithSDK", func(t *testing.T) { // 尝试使用测试用户登录 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可用于创建新的认证客户端 t.Run("CreateTokenClient", func(t *testing.T) { tokenClient, err := configure.NewTokenAuthClient("http://localhost:8080", token) if err != nil { t.Fatalf("使用获取的token创建客户端失败: %v", err) } t.Logf("Token客户端创建成功,BaseURL: %s", tokenClient.GetConfig().BaseURL) // 测试token客户端是否可正常工作(查询表列表) query := &configure.DicTableQueryRequest{ QueryRequest: queryreq.QueryRequest{ Page: 0, PageSize: 5, }, } result, err := tokenClient.ListTables(ctx, query) if err != nil { // 如果token无效或权限不足,可能失败 t.Logf("使用token查询表列表失败(可能是token权限问题): %v", err) } else { t.Logf("使用token查询表列表成功,总记录数: %d", result.TotalCount) } }) }) // 测试无效凭证登录 t.Run("InvalidCredentialsLogin", func(t *testing.T) { req := &configure.UserLoginRequest{ UserID: "invalid-user", Password: "wrong-password", } _, err := client.LoginUser(ctx, req) if err == nil { t.Fatal("使用无效凭证登录应该失败,但成功了") } // 检查错误类型 t.Logf("无效凭证登录失败(预期): %v", err) }) } // 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") } // min 返回两个整数的最小值 func min(a, b int) int { if a < b { return a } return b }