package app import ( "encoding/json" "os" "path/filepath" "strings" "testing" . "code.hoteas.com/golang/hotime" ) var ProjectTest = ProjTest{ "test": TestTest, "mysql": MysqlTest, "dmdb": DmdbTest, "cache": CacheTest, "expect_demo": ExpectDemoTest, } var testApp *TestApp func TestMain(m *testing.M) { testApp = Test("config/config.json"). WorkDir(".."). Listener(func(ctx *Context) bool { return false // 空实现:继续进控制器;需要鉴权/拦截时在此写 }). Swagger("My API", "1.0.0"). Proj(TestProj{ "app": { Proj: Project, Tests: ProjectTest, }, }). Flows(DemoFlows) // 仅本 example:空库时灌演示数据。须连专用测试库,勿对生产库调用。 SetupDatabase(&testApp.Db) os.Exit(testApp.Run(m)) } func TestApi(t *testing.T) { testApp.RunTests(t) } // 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() } // 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") } }