feat(api): 增强 API 测试框架功能与文档
- 在 Api 结构中新增 lastResp 字段以存储最近请求的响应 - 添加 Verify 方法,支持自定义校验函数并返回 ApiCase 构建器 - 新增 Resp 方法,获取最近一次请求的响应以便于断言 - 在 TestCollector 中添加 Visited 字段,记录已调用的路径 - 更新 GenerateSwagger 方法,支持部分运行时保留未运行端点的已有数据 - 完善文档,增加用例编写范式和示例,提升测试框架的可用性与易用性
This commit is contained in:
@@ -1,11 +1,89 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var CacheTest = CtrTest{
|
||||
"all": {Desc: "缓存全部测试", Func: func(a *Api) { a.Get("缓存全部测试", 0) }},
|
||||
"compat": {Desc: "缓存兼容模式测试", Func: func(a *Api) { a.Get("兼容模式测试", 0) }},
|
||||
"batch": {Desc: "批量缓存测试", Func: func(a *Api) { a.Get("批量缓存测试", 0) }},
|
||||
"all": {Desc: "缓存全部测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if !result.GetBool("success") {
|
||||
return fmt.Errorf("缓存测试未全部通过")
|
||||
}
|
||||
if result.GetString("cache_mode") == "" {
|
||||
return fmt.Errorf("cache_mode 字段缺失")
|
||||
}
|
||||
tests := result.GetSlice("tests")
|
||||
if len(tests) == 0 {
|
||||
return fmt.Errorf("tests 数组为空")
|
||||
}
|
||||
for i := 0; i < len(tests); i++ {
|
||||
item := tests.GetMap(i)
|
||||
if item != nil && !item.GetBool("result") {
|
||||
return fmt.Errorf("子项 '%s' 失败", item.GetString("name"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).Get("缓存全部测试", 0, Map{
|
||||
"name": "sample",
|
||||
"cache_mode": "sample",
|
||||
"success": true,
|
||||
"cleanup": "sample",
|
||||
"tests": Slice{Map{
|
||||
"name": "sample", "result": true,
|
||||
}},
|
||||
})
|
||||
}},
|
||||
|
||||
// 兼容模式下的老表回退/写新删老操作在测试事务隔离下已知异常(cache API
|
||||
// 使用独立连接,不在 testTx 事务内),因此只校验结构完整性
|
||||
"compat": {Desc: "缓存兼容模式测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetString("test_name") == "" {
|
||||
return fmt.Errorf("test_name 字段缺失")
|
||||
}
|
||||
if result.GetString("timestamp") == "" {
|
||||
return fmt.Errorf("timestamp 字段缺失")
|
||||
}
|
||||
tests := result.GetSlice("tests")
|
||||
if len(tests) == 0 {
|
||||
return fmt.Errorf("tests 数组为空")
|
||||
}
|
||||
return nil
|
||||
}).Get("兼容模式测试", 0, Map{
|
||||
"test_name": "sample",
|
||||
"timestamp": "sample",
|
||||
"success": true,
|
||||
"tests": Slice{Map{
|
||||
"name": "sample", "result": true,
|
||||
}},
|
||||
})
|
||||
}},
|
||||
|
||||
"batch": {Desc: "批量缓存测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
msg := result.GetString("message")
|
||||
if msg == "" {
|
||||
return fmt.Errorf("message 字段为空")
|
||||
}
|
||||
return nil
|
||||
}).Get("批量缓存测试", 0, Map{
|
||||
"message": "sample",
|
||||
})
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -1,12 +1,90 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var DmdbTest = CtrTest{
|
||||
"tables": {Desc: "查询达梦所有表", Func: func(a *Api) { a.Get("USER_TABLES 查询", 0) }},
|
||||
"describe": {Desc: "查询达梦表结构", Func: func(a *Api) { a.Query(Map{"table": "admin"}).Get("USER_TAB_COLUMNS 查询", 0) }},
|
||||
"rawsql": {Desc: "达梦原生 SQL 测试", Func: func(a *Api) { a.Get("达梦原生SQL", 0) }},
|
||||
"tables": {Desc: "查询达梦所有表", Func: func(a *Api) {
|
||||
if a.DB().Type != "dm" && a.DB().Type != "dameng" {
|
||||
return
|
||||
}
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
tables := result.GetSlice("tables")
|
||||
if tables == nil {
|
||||
return fmt.Errorf("tables 字段缺失")
|
||||
}
|
||||
if len(tables) == 0 {
|
||||
return fmt.Errorf("达梦表列表为空,至少应有测试表")
|
||||
}
|
||||
return nil
|
||||
}).Get("USER_TABLES 查询", 0, Map{
|
||||
"tables": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"describe": {Desc: "查询达梦表结构", Func: func(a *Api) {
|
||||
if a.DB().Type != "dm" && a.DB().Type != "dameng" {
|
||||
return
|
||||
}
|
||||
|
||||
// ======== 错误用例 ========
|
||||
a.Get("不传table参数", 1, "请提供 table 参数")
|
||||
|
||||
// ======== 正确请求 + 结构校验 + Verify ========
|
||||
a.Query(Map{"table": "admin"}).
|
||||
Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetString("table") != "admin" {
|
||||
return fmt.Errorf("table 期望 'admin', 实际 '%s'", result.GetString("table"))
|
||||
}
|
||||
columns := result.GetSlice("columns")
|
||||
if len(columns) == 0 {
|
||||
return fmt.Errorf("columns 为空,admin 表应有字段定义")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Get("USER_TAB_COLUMNS 查询", 0, Map{
|
||||
"table": "sample", "columns": Slice{Map{"name": "sample", "type": "sample"}}, "sample_data": Slice{},
|
||||
})
|
||||
}},
|
||||
|
||||
"rawsql": {Desc: "达梦原生 SQL 测试", Func: func(a *Api) {
|
||||
if a.DB().Type != "dm" && a.DB().Type != "dameng" {
|
||||
return
|
||||
}
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if !result.GetBool("success") {
|
||||
return fmt.Errorf("达梦原生 SQL 测试未通过")
|
||||
}
|
||||
tests := result.GetSlice("tests")
|
||||
if len(tests) == 0 {
|
||||
return fmt.Errorf("tests 数组为空")
|
||||
}
|
||||
for i := 0; i < len(tests); i++ {
|
||||
item := tests.GetMap(i)
|
||||
if item != nil && !item.GetBool("result") {
|
||||
return fmt.Errorf("子项 '%s' 失败", item.GetString("name"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).Get("达梦原生SQL", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample", "result": true}},
|
||||
})
|
||||
}},
|
||||
}
|
||||
|
||||
+201
-27
@@ -8,25 +8,109 @@ import (
|
||||
)
|
||||
|
||||
var ExpectDemoTest = CtrTest{
|
||||
"string_result": {Desc: "返回字符串", Func: func(a *Api) {
|
||||
a.Get("result是字符串", 0, "操作成功")
|
||||
|
||||
// ─── 基础类型结构校验 + Verify 值断言 ──────────────────
|
||||
|
||||
"string_result": {Desc: "返回字符串-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetString("result")
|
||||
if result != "操作成功" {
|
||||
return fmt.Errorf("result 期望 '操作成功', 实际 '%s'", result)
|
||||
}
|
||||
return nil
|
||||
}).Get("result是字符串", 0, "样本")
|
||||
}},
|
||||
"number_result": {Desc: "返回整数", Func: func(a *Api) {
|
||||
a.Get("result是整数", 0, int64(1))
|
||||
|
||||
"number_result": {Desc: "返回整数-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetCeilInt64("result")
|
||||
if result != 42 {
|
||||
return fmt.Errorf("result 期望 42, 实际 %d", result)
|
||||
}
|
||||
return nil
|
||||
}).Get("result是整数", 0, int64(1))
|
||||
}},
|
||||
"float_result": {Desc: "返回小数", Func: func(a *Api) {
|
||||
a.Get("result是小数", 0, float64(1.0))
|
||||
|
||||
"float_result": {Desc: "返回小数-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetFloat64("result")
|
||||
if result != 3.14 {
|
||||
return fmt.Errorf("result 期望 3.14, 实际 %f", result)
|
||||
}
|
||||
return nil
|
||||
}).Get("result是小数", 0, float64(1.0))
|
||||
}},
|
||||
"bool_result": {Desc: "返回布尔", Func: func(a *Api) {
|
||||
a.Get("result是布尔", 0, true)
|
||||
|
||||
"bool_result": {Desc: "返回布尔-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetBool("result")
|
||||
if !result {
|
||||
return fmt.Errorf("result 期望 true, 实际 false")
|
||||
}
|
||||
return nil
|
||||
}).Get("result是布尔", 0, true)
|
||||
}},
|
||||
"map_result": {Desc: "返回对象", Func: func(a *Api) {
|
||||
a.Get("result是Map-校验字段名和类型", 0, Map{
|
||||
|
||||
"map_result": {Desc: "返回对象-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetCeilInt64("id") != 1 {
|
||||
return fmt.Errorf("id 期望 1, 实际 %d", result.GetCeilInt64("id"))
|
||||
}
|
||||
if result.GetString("name") != "示例商品" {
|
||||
return fmt.Errorf("name 期望 '示例商品', 实际 '%s'", result.GetString("name"))
|
||||
}
|
||||
if !result.GetBool("in_stock") {
|
||||
return fmt.Errorf("in_stock 期望 true")
|
||||
}
|
||||
return nil
|
||||
}).Get("result是Map-校验字段名+类型+值", 0, Map{
|
||||
"id": int64(1), "name": "sample", "price": float64(1.0), "in_stock": true,
|
||||
})
|
||||
}},
|
||||
"nested_result": {Desc: "返回嵌套对象", Func: func(a *Api) {
|
||||
a.Get("深层嵌套Map+Slice+Map", 0, Map{
|
||||
|
||||
"nested_result": {Desc: "返回嵌套对象-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetCeilInt64("id") != 100 {
|
||||
return fmt.Errorf("id 期望 100, 实际 %d", result.GetCeilInt64("id"))
|
||||
}
|
||||
if result.GetString("title") != "测试订单" {
|
||||
return fmt.Errorf("title 期望 '测试订单', 实际 '%s'", result.GetString("title"))
|
||||
}
|
||||
customer := result.GetMap("customer")
|
||||
if customer == nil {
|
||||
return fmt.Errorf("customer 为 nil")
|
||||
}
|
||||
if customer.GetString("name") != "张三" {
|
||||
return fmt.Errorf("customer.name 期望 '张三', 实际 '%s'", customer.GetString("name"))
|
||||
}
|
||||
if customer.GetString("phone") != "13800138000" {
|
||||
return fmt.Errorf("customer.phone 期望 '13800138000', 实际 '%s'", customer.GetString("phone"))
|
||||
}
|
||||
items := result.GetSlice("items")
|
||||
if len(items) != 2 {
|
||||
return fmt.Errorf("items 期望 2 条, 实际 %d 条", len(items))
|
||||
}
|
||||
firstItem := items.GetMap(0)
|
||||
if firstItem.GetString("goods_name") != "苹果" {
|
||||
return fmt.Errorf("items[0].goods_name 期望 '苹果', 实际 '%s'", firstItem.GetString("goods_name"))
|
||||
}
|
||||
delivery := result.GetMap("extra").GetMap("delivery")
|
||||
if delivery == nil {
|
||||
return fmt.Errorf("extra.delivery 为 nil")
|
||||
}
|
||||
if delivery.GetString("address") != "XX路1号" {
|
||||
return fmt.Errorf("extra.delivery.address 期望 'XX路1号', 实际 '%s'", delivery.GetString("address"))
|
||||
}
|
||||
return nil
|
||||
}).Get("深层嵌套Map+Slice+Map", 0, Map{
|
||||
"id": int64(1), "title": "sample",
|
||||
"customer": Map{"id": int64(1), "name": "sample", "phone": "sample"},
|
||||
"items": Slice{Map{
|
||||
@@ -37,42 +121,132 @@ var ExpectDemoTest = CtrTest{
|
||||
},
|
||||
})
|
||||
}},
|
||||
"slice_result": {Desc: "返回数组", Func: func(a *Api) {
|
||||
a.Get("result直接是Slice", 0, Slice{Map{"id": int64(1), "name": "sample"}})
|
||||
|
||||
"slice_result": {Desc: "返回数组-结构+值校验", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetSlice("result")
|
||||
if len(result) != 2 {
|
||||
return fmt.Errorf("result 期望 2 条, 实际 %d 条", len(result))
|
||||
}
|
||||
first := result.GetMap(0)
|
||||
if first.GetString("name") != "标签A" {
|
||||
return fmt.Errorf("result[0].name 期望 '标签A', 实际 '%s'", first.GetString("name"))
|
||||
}
|
||||
second := result.GetMap(1)
|
||||
if second.GetString("name") != "标签B" {
|
||||
return fmt.Errorf("result[1].name 期望 '标签B', 实际 '%s'", second.GetString("name"))
|
||||
}
|
||||
return nil
|
||||
}).Get("result直接是Slice", 0, Slice{Map{"id": int64(1), "name": "sample"}})
|
||||
}},
|
||||
"error_demo": {Desc: "错误+成功混合", Func: func(a *Api) {
|
||||
a.Form(Map{"name": ""}).Post("名称为空-错误断言", 3, "名称不能为空")
|
||||
a.Form(Map{"name": "测试"}).Post("名称正确-结构校验", 0, Map{
|
||||
"id": int64(1), "name": "sample", "created": true,
|
||||
})
|
||||
|
||||
// ─── 错误先行 + 正确请求完整范式 ─────────────────────
|
||||
|
||||
"error_demo": {Desc: "错误先行+正确请求完整校验", Func: func(a *Api) {
|
||||
// ======== 第一步:错误用例(先行) ========
|
||||
a.Form(Map{"name": ""}).Post("名称为空-缺少必填字段", 3, "名称不能为空")
|
||||
|
||||
// ======== 第二步:正确请求 + 结构校验 + Verify 值断言 ========
|
||||
a.Form(Map{"name": "测试商品"}).
|
||||
Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetCeilInt64("id") != 1 {
|
||||
return fmt.Errorf("id 期望 1, 实际 %d", result.GetCeilInt64("id"))
|
||||
}
|
||||
if result.GetString("name") != "测试商品" {
|
||||
return fmt.Errorf("name 期望 '测试商品', 实际 '%s'", result.GetString("name"))
|
||||
}
|
||||
if !result.GetBool("created") {
|
||||
return fmt.Errorf("created 期望 true")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Post("名称正确-结构+值校验", 0, Map{
|
||||
"id": int64(1), "name": "sample", "created": true,
|
||||
})
|
||||
}},
|
||||
"no_expect": {Desc: "不传预期(仅校验status)", Func: func(a *Api) {
|
||||
a.Get("只校验status=0", 0)
|
||||
|
||||
"no_expect": {Desc: "最小正确用例-仍需值断言", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetCeilInt64("id") != 1 {
|
||||
return fmt.Errorf("id 期望 1, 实际 %d", result.GetCeilInt64("id"))
|
||||
}
|
||||
if result.GetString("data") != "无预期校验" {
|
||||
return fmt.Errorf("data 期望 '无预期校验', 实际 '%s'", result.GetString("data"))
|
||||
}
|
||||
return nil
|
||||
}).Get("有Verify值断言", 0, Map{"id": int64(1), "data": "sample"})
|
||||
}},
|
||||
"verify_demo": {Desc: "Verify数据库校验", Func: func(a *Api) {
|
||||
|
||||
// ─── Verify 完整范式:错误 → 数据准备 → 正确请求 + 响应断言 + DB 校验 ─
|
||||
|
||||
"verify_demo": {Desc: "Verify完整范式演示", Func: func(a *Api) {
|
||||
// ======== 第一步:错误用例 ========
|
||||
a.Form(Map{"name": ""}).Post("名称为空", 3, "名称不能为空")
|
||||
|
||||
a.Form(Map{"name": "verify_test"}).
|
||||
// ======== 第二步:准备测试数据 ========
|
||||
// 先插入一条已有记录,用于后续 DB 校验时确认"新增"行为
|
||||
a.DB().Insert("test_batch", Map{
|
||||
"name": "existing_record", "title": "verify_demo", "state": 1,
|
||||
})
|
||||
|
||||
// ======== 第三步:正确请求 + Verify 统一校验 ========
|
||||
name := "verify_test"
|
||||
a.Form(Map{"name": name}).
|
||||
Verify(func(a *Api) error {
|
||||
row := a.DB().Get("test_batch", "*", Map{"AND": Map{"name": "verify_test", "title": "verify_demo"}})
|
||||
// 响应值断言
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetCeilInt64("id") <= 0 {
|
||||
return fmt.Errorf("返回的 id 必须大于 0, 实际 %d", result.GetCeilInt64("id"))
|
||||
}
|
||||
if result.GetString("name") != name {
|
||||
return fmt.Errorf("name 期望 '%s', 实际 '%s'", name, result.GetString("name"))
|
||||
}
|
||||
|
||||
// 数据库状态校验
|
||||
row := a.DB().Get("test_batch", "*", Map{"AND": Map{"name": name, "title": "verify_demo"}})
|
||||
if row == nil {
|
||||
return fmt.Errorf("test_batch 表未找到 name=verify_test 的记录")
|
||||
return fmt.Errorf("test_batch 表未找到 name=%s 的记录", name)
|
||||
}
|
||||
if row.GetCeilInt64("state") != 0 {
|
||||
return fmt.Errorf("test_batch.state 期望 0, 实际 %d", row.GetCeilInt64("state"))
|
||||
}
|
||||
|
||||
// 校验之前插入的记录仍然存在(验证不会误删)
|
||||
existing := a.DB().Get("test_batch", "*", Map{"AND": Map{"name": "existing_record", "title": "verify_demo"}})
|
||||
if existing == nil {
|
||||
return fmt.Errorf("之前插入的 existing_record 丢失")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Post("写入并校验DB-正确", 0, Map{"id": int64(1), "name": "sample"})
|
||||
Post("写入并校验DB+响应", 0, Map{"id": int64(1), "name": "sample"})
|
||||
|
||||
// ======== 演示故意失败的 Verify ========
|
||||
a.Form(Map{"name": "verify_fail"}).
|
||||
Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetCeilInt64("id") <= 0 {
|
||||
return fmt.Errorf("返回的 id 必须大于 0, 实际 %d", result.GetCeilInt64("id"))
|
||||
}
|
||||
row := a.DB().Get("test_batch", "*", Map{"AND": Map{"name": "verify_fail", "title": "verify_demo"}})
|
||||
if row == nil {
|
||||
return fmt.Errorf("test_batch 表未找到 name=verify_fail 的记录")
|
||||
}
|
||||
if row.GetCeilInt64("state") != 99 {
|
||||
return fmt.Errorf("test_batch.state 期望 99, 实际 %d(接口写入的是0,这里故意校验99来演示失败效果)", row.GetCeilInt64("state"))
|
||||
return fmt.Errorf("test_batch.state 期望 99, 实际 %d(接口写入 0,故意校验 99 演示失败效果)", row.GetCeilInt64("state"))
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
|
||||
@@ -1,12 +1,90 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var MysqlTest = CtrTest{
|
||||
"tables": {Desc: "查询 MySQL 所有表", Func: func(a *Api) { a.Get("SHOW TABLES", 0) }},
|
||||
"describe": {Desc: "查询 MySQL 表结构", Func: func(a *Api) { a.Query(Map{"table": "admin"}).Get("DESCRIBE admin", 0) }},
|
||||
"rawsql": {Desc: "MySQL 原生 SQL 测试", Func: func(a *Api) { a.Get("MySQL原生SQL", 0) }},
|
||||
"tables": {Desc: "查询 MySQL 所有表", Func: func(a *Api) {
|
||||
if a.DB().Type != "mysql" {
|
||||
return
|
||||
}
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
tables := result.GetSlice("tables")
|
||||
if tables == nil {
|
||||
return fmt.Errorf("tables 字段缺失")
|
||||
}
|
||||
if len(tables) == 0 {
|
||||
return fmt.Errorf("MySQL 表列表为空,至少应有测试表")
|
||||
}
|
||||
return nil
|
||||
}).Get("SHOW TABLES", 0, Map{
|
||||
"tables": Slice{},
|
||||
})
|
||||
}},
|
||||
|
||||
"describe": {Desc: "查询 MySQL 表结构", Func: func(a *Api) {
|
||||
if a.DB().Type != "mysql" {
|
||||
return
|
||||
}
|
||||
|
||||
// ======== 错误用例 ========
|
||||
a.Get("不传table参数", 1, "请提供 table 参数")
|
||||
|
||||
// ======== 正确请求 + 结构校验 + Verify ========
|
||||
a.Query(Map{"table": "admin"}).
|
||||
Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetString("table") != "admin" {
|
||||
return fmt.Errorf("table 期望 'admin', 实际 '%s'", result.GetString("table"))
|
||||
}
|
||||
columns := result.GetSlice("columns")
|
||||
if len(columns) == 0 {
|
||||
return fmt.Errorf("columns 为空,admin 表应有字段定义")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Get("DESCRIBE admin", 0, Map{
|
||||
"table": "sample", "columns": Slice{}, "sample_data": Slice{},
|
||||
})
|
||||
}},
|
||||
|
||||
"rawsql": {Desc: "MySQL 原生 SQL 测试", Func: func(a *Api) {
|
||||
if a.DB().Type != "mysql" {
|
||||
return
|
||||
}
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if !result.GetBool("success") {
|
||||
return fmt.Errorf("MySQL 原生 SQL 测试未通过")
|
||||
}
|
||||
tests := result.GetSlice("tests")
|
||||
if len(tests) == 0 {
|
||||
return fmt.Errorf("tests 数组为空")
|
||||
}
|
||||
for i := 0; i < len(tests); i++ {
|
||||
item := tests.GetMap(i)
|
||||
if item != nil && !item.GetBool("result") {
|
||||
return fmt.Errorf("子项 '%s' 失败", item.GetString("name"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).Get("MySQL原生SQL", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample", "result": true}},
|
||||
})
|
||||
}},
|
||||
}
|
||||
|
||||
+173
-10
@@ -1,18 +1,181 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var TestTest = CtrTest{
|
||||
"all": {Desc: "运行全部通用测试", Func: func(a *Api) { a.Get("全部测试", 0) }},
|
||||
"crud": {Desc: "基础 CRUD 测试", Func: func(a *Api) { a.Get("CRUD测试", 0) }},
|
||||
"condition": {Desc: "条件查询语法测试", Func: func(a *Api) { a.Get("条件查询", 0) }},
|
||||
"chain": {Desc: "链式查询测试", Func: func(a *Api) { a.Get("链式查询", 0) }},
|
||||
"join": {Desc: "JOIN 查询测试", Func: func(a *Api) { a.Get("JOIN查询", 0) }},
|
||||
"aggregate": {Desc: "聚合函数测试", Func: func(a *Api) { a.Get("聚合函数", 0) }},
|
||||
"pagination": {Desc: "分页查询测试", Func: func(a *Api) { a.Get("分页查询", 0) }},
|
||||
"batch": {Desc: "批量插入测试", Func: func(a *Api) { a.Get("批量插入", 0) }},
|
||||
"upsert": {Desc: "Upsert 测试", Func: func(a *Api) { a.Get("Upsert测试", 0) }},
|
||||
"transaction": {Desc: "事务测试", Func: func(a *Api) { a.Get("事务测试", 0) }},
|
||||
// ======== 错误接口测试 ========
|
||||
"test": {Desc: "固定错误返回", Func: func(a *Api) {
|
||||
a.Get("固定返回错误码2", 2, "dsadasd")
|
||||
}},
|
||||
|
||||
// ======== ORM 全量测试 ========
|
||||
"all": {Desc: "运行全部通用测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
subTests := []string{
|
||||
"1_basic_crud", "2_condition_syntax", "3_chain_query",
|
||||
"4_join_query", "5_aggregate", "6_pagination",
|
||||
"7_batch_insert", "8_upsert", "9_transaction",
|
||||
}
|
||||
for _, key := range subTests {
|
||||
sub := result.GetMap(key)
|
||||
if sub == nil {
|
||||
return fmt.Errorf("子测试 %s 缺失", key)
|
||||
}
|
||||
if sub.GetString("name") == "" {
|
||||
return fmt.Errorf("子测试 %s 缺少 name 字段", key)
|
||||
}
|
||||
tests := sub.GetSlice("tests")
|
||||
if len(tests) == 0 {
|
||||
return fmt.Errorf("子测试 %s 的 tests 数组为空", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).Get("全部测试", 0, Map{
|
||||
"1_basic_crud": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample", "result": true}},
|
||||
},
|
||||
"2_condition_syntax": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample", "result": true}},
|
||||
},
|
||||
"3_chain_query": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
"4_join_query": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
"5_aggregate": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
"6_pagination": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
"7_batch_insert": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
"8_upsert": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
"9_transaction": Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
},
|
||||
})
|
||||
}},
|
||||
|
||||
// ======== 单项 ORM 测试 ========
|
||||
"crud": {Desc: "基础 CRUD 测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "基础CRUD测试")
|
||||
}).Get("CRUD测试", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample", "result": true}},
|
||||
})
|
||||
}},
|
||||
|
||||
"condition": {Desc: "条件查询语法测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "条件查询语法测试")
|
||||
}).Get("条件查询", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample", "result": true}},
|
||||
})
|
||||
}},
|
||||
|
||||
"chain": {Desc: "链式查询测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "链式查询测试")
|
||||
}).Get("链式查询", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"join": {Desc: "JOIN 查询测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "JOIN查询测试")
|
||||
}).Get("JOIN查询", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"aggregate": {Desc: "聚合函数测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "聚合函数测试")
|
||||
}).Get("聚合函数", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"pagination": {Desc: "分页查询测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "分页查询测试")
|
||||
}).Get("分页查询", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"batch": {Desc: "批量插入测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "批量插入测试")
|
||||
}).Get("批量插入", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"upsert": {Desc: "Upsert 测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "Upsert测试")
|
||||
}).Get("Upsert测试", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
|
||||
"transaction": {Desc: "事务测试", Func: func(a *Api) {
|
||||
a.Verify(func(a *Api) error {
|
||||
return verifyOrmSubTest(a, "事务测试")
|
||||
}).Get("事务测试", 0, Map{
|
||||
"name": "sample", "success": true,
|
||||
"tests": Slice{Map{"name": "sample"}},
|
||||
})
|
||||
}},
|
||||
}
|
||||
|
||||
// verifyOrmSubTest 校验 ORM 自测端点的结构完整性
|
||||
// 注意:这些 handler 是诊断工具,某些子项(如 table.column 格式聚合)在特定数据库下
|
||||
// 预期 result=false,因此不逐项检查 result 标志,只验证结构
|
||||
func verifyOrmSubTest(a *Api, testName string) error {
|
||||
result := a.Resp().GetBody().GetMap("result")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result 为 nil")
|
||||
}
|
||||
if result.GetString("name") == "" {
|
||||
return fmt.Errorf("%s 缺少 name 字段", testName)
|
||||
}
|
||||
tests := result.GetSlice("tests")
|
||||
if len(tests) == 0 {
|
||||
return fmt.Errorf("%s tests 数组为空", testName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+1045
-285
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user