2026-03-20 10:46:51 +08:00
|
|
|
|
package app
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-13 18:11:08 +08:00
|
|
|
|
"encoding/json"
|
2026-03-20 10:46:51 +08:00
|
|
|
|
"os"
|
2026-07-13 18:11:08 +08:00
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
2026-03-20 10:46:51 +08:00
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
|
|
. "code.hoteas.com/golang/hotime"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var ProjectTest = ProjTest{
|
2026-03-21 13:38:22 +08:00
|
|
|
|
"test": TestTest,
|
|
|
|
|
|
"mysql": MysqlTest,
|
|
|
|
|
|
"dmdb": DmdbTest,
|
|
|
|
|
|
"cache": CacheTest,
|
|
|
|
|
|
"expect_demo": ExpectDemoTest,
|
2026-03-20 10:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var testApp *TestApp
|
|
|
|
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
2026-07-13 18:11:08 +08:00
|
|
|
|
testApp = Test("config/config.json").
|
|
|
|
|
|
WorkDir("..").
|
2026-07-13 19:56:36 +08:00
|
|
|
|
Listener(func(ctx *Context) bool {
|
|
|
|
|
|
return false // 空实现:继续进控制器;需要鉴权/拦截时在此写
|
|
|
|
|
|
}).
|
2026-07-13 18:11:08 +08:00
|
|
|
|
Swagger("My API", "1.0.0").
|
|
|
|
|
|
Proj(TestProj{
|
2026-03-20 10:46:51 +08:00
|
|
|
|
"app": {
|
|
|
|
|
|
Proj: Project,
|
|
|
|
|
|
Tests: ProjectTest,
|
|
|
|
|
|
},
|
2026-07-13 18:11:08 +08:00
|
|
|
|
}).
|
|
|
|
|
|
Flows(DemoFlows)
|
2026-03-20 10:46:51 +08:00
|
|
|
|
|
2026-07-13 18:11:08 +08:00
|
|
|
|
// 仅本 example:空库时灌演示数据。须连专用测试库,勿对生产库调用。
|
2026-03-20 10:46:51 +08:00
|
|
|
|
SetupDatabase(&testApp.Db)
|
|
|
|
|
|
|
2026-07-13 18:11:08 +08:00
|
|
|
|
os.Exit(testApp.Run(m))
|
2026-03-20 10:46:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestApi(t *testing.T) {
|
|
|
|
|
|
testApp.RunTests(t)
|
|
|
|
|
|
}
|
2026-07-13 18:11:08 +08:00
|
|
|
|
|
2026-07-13 19:56:36 +08:00
|
|
|
|
// TestListenerChain 验证链式 Listener 在请求路径上被调用。
|
|
|
|
|
|
// 注意:TestMain 已 Chdir 到 example/,此处不再 WorkDir。
|
|
|
|
|
|
func TestListenerChain(t *testing.T) {
|
|
|
|
|
|
hit := false
|
|
|
|
|
|
app := Test("config/config.json").
|
|
|
|
|
|
Listener(func(ctx *Context) bool {
|
|
|
|
|
|
hit = true
|
|
|
|
|
|
return false
|
|
|
|
|
|
}).
|
|
|
|
|
|
Proj(TestProj{
|
|
|
|
|
|
"app": {
|
|
|
|
|
|
Proj: Project,
|
|
|
|
|
|
Tests: ProjTest{
|
|
|
|
|
|
"expect_demo": CtrTest{
|
|
|
|
|
|
"string_result": {Desc: "触发 listener", Func: func(a *Api) {
|
|
|
|
|
|
ExpectDemoTest["string_result"].Func(a)
|
|
|
|
|
|
}},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
SetupDatabase(&app.Db)
|
|
|
|
|
|
app.RunTests(t)
|
|
|
|
|
|
|
|
|
|
|
|
if !hit {
|
|
|
|
|
|
t.Fatal("Listener 未被调用")
|
|
|
|
|
|
}
|
|
|
|
|
|
_ = app.Db.RollbackTestTx()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-13 18:11:08 +08:00
|
|
|
|
// TestInterruptStop 模拟提前中断:string_result 跑完后 RequestStop,zzz_should_skip 应 Skip;
|
|
|
|
|
|
// swagger 中 string_result 的 cases 应保留。注意:TestMain 已 Chdir 到 example/,此处不再 WorkDir。
|
|
|
|
|
|
func TestInterruptStop(t *testing.T) {
|
|
|
|
|
|
var app *TestApp
|
|
|
|
|
|
secondRan := false
|
|
|
|
|
|
|
|
|
|
|
|
app = Test("config/config.json").
|
|
|
|
|
|
Swagger("Interrupt Demo", "1.0.0").
|
|
|
|
|
|
Proj(TestProj{
|
|
|
|
|
|
"app": {
|
|
|
|
|
|
Proj: Project,
|
|
|
|
|
|
Tests: ProjTest{
|
|
|
|
|
|
"expect_demo": CtrTest{
|
|
|
|
|
|
"string_result": {Desc: "先跑并停测", Func: func(a *Api) {
|
|
|
|
|
|
ExpectDemoTest["string_result"].Func(a)
|
|
|
|
|
|
app.RequestStop()
|
|
|
|
|
|
}},
|
|
|
|
|
|
"zzz_should_skip": {Desc: "应被跳过", Func: func(a *Api) {
|
|
|
|
|
|
secondRan = true
|
|
|
|
|
|
t.Error("zzz_should_skip 在 RequestStop 后不应执行")
|
|
|
|
|
|
}},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
SetupDatabase(&app.Db)
|
|
|
|
|
|
app.RunTests(t)
|
|
|
|
|
|
|
|
|
|
|
|
if secondRan {
|
|
|
|
|
|
t.Fatal("停测后仍执行了后续接口")
|
|
|
|
|
|
}
|
|
|
|
|
|
_ = app.Db.RollbackTestTx()
|
|
|
|
|
|
|
|
|
|
|
|
// 收尾写盘(中断路径下可能未跑满 GenerateSwagger,显式补一次增量合并)
|
|
|
|
|
|
if err := app.GenerateSwagger("Interrupt Demo", "1.0.0", app.Config.GetString("tpt")); err != nil {
|
|
|
|
|
|
t.Fatalf("GenerateSwagger: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
specPath := filepath.Join(app.Config.GetString("tpt"), "swagger", "app", "api-spec.json")
|
|
|
|
|
|
data, err := os.ReadFile(specPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("应已生成 swagger %s: %v", specPath, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
var spec struct {
|
|
|
|
|
|
Endpoints []struct {
|
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
|
Cases json.RawMessage `json:"cases"`
|
|
|
|
|
|
} `json:"endpoints"`
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := json.Unmarshal(data, &spec); err != nil {
|
|
|
|
|
|
t.Fatalf("解析 api-spec 失败: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
found := false
|
|
|
|
|
|
for _, ep := range spec.Endpoints {
|
|
|
|
|
|
if ep.Path != "/app/expect_demo/string_result" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
found = true
|
|
|
|
|
|
if !strings.Contains(string(ep.Cases), `"name"`) {
|
|
|
|
|
|
t.Fatalf("string_result 应保留测试 cases,实际: %s", string(ep.Cases))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !found {
|
|
|
|
|
|
t.Fatal("swagger 中应包含已跑完的 /app/expect_demo/string_result")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|