feat(testing): Flows/FromCase、增量 swagger 与 Doc-Driven 门禁

补齐多接口流程联调与控制台体验,并落地仓内 Doc-Driven+TDD 总规则与文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 18:11:08 +08:00
parent 9fe4ab2758
commit 16496a8dc0
14 changed files with 4178 additions and 418 deletions
+103 -26
View File
@@ -44,30 +44,51 @@ type ApiTestDef struct {
// Api 测试 API 入口,由 RunTests 自动创建并注入
type Api struct {
app *TestApp
path string
t *testing.T
session Map
lastResp *ApiResponse
app *TestApp
path string
t *testing.T
session Map
lastResp *ApiResponse
flowName string
flowGroup string
flowDesc string
flowPrep string
flowStep string
flowStepIndex int
flowParent *Flow // 业务流程内执行时回写 lastResp
}
// WithSession 返回一个携带 session 的新 Api 实例
func (a *Api) WithSession(s Map) *Api {
return &Api{
app: a.app,
path: a.path,
t: a.t,
session: s,
app: a.app,
path: a.path,
t: a.t,
session: s,
flowName: a.flowName,
flowGroup: a.flowGroup,
flowDesc: a.flowDesc,
flowPrep: a.flowPrep,
flowStep: a.flowStep,
flowStepIndex: a.flowStepIndex,
flowParent: a.flowParent,
}
}
// AtPath 返回指向其它 API 路径的 Api(跨接口断言时用)
func (a *Api) AtPath(path string) *Api {
return &Api{
app: a.app,
path: path,
t: a.t,
session: a.session,
app: a.app,
path: path,
t: a.t,
session: a.session,
flowName: a.flowName,
flowGroup: a.flowGroup,
flowDesc: a.flowDesc,
flowPrep: a.flowPrep,
flowStep: a.flowStep,
flowStepIndex: a.flowStepIndex,
flowParent: a.flowParent,
}
}
@@ -79,14 +100,29 @@ func (a *Api) RunSub(desc string, fn func(a *Api)) {
}
a.t.Run(desc, func(t *testing.T) {
fn(&Api{
app: a.app,
path: a.path,
t: t,
session: a.session,
app: a.app,
path: a.path,
t: t,
session: a.session,
flowName: a.flowName,
flowGroup: a.flowGroup,
flowDesc: a.flowDesc,
flowPrep: a.flowPrep,
flowStep: a.flowStep,
flowStepIndex: a.flowStepIndex,
flowParent: a.flowParent,
})
})
}
// FromCase 绑定单接口已有验收用例:加载其请求模板;跑通后回写该 case 的响应
func (a *Api) FromCase(caseName string) *ApiCase {
c := a.newCase()
c.bindCase = caseName
c.applyCaseTemplate(a.app.findCaseTemplate(a.path, caseName))
return c
}
// JSON 设置 JSON body,返回 ApiCase 构建器
func (a *Api) JSON(body interface{}) *ApiCase {
c := a.newCase()
@@ -184,6 +220,34 @@ type ApiCase struct {
fileContent []byte
note string
verifyFn func(a *Api) error
bindCase string // 绑定的单接口验收用例名(FromCase)
}
// FromCase 在已有 ApiCase 上绑定验收用例名(若尚未加载模板则加载)
func (c *ApiCase) FromCase(caseName string) *ApiCase {
c.bindCase = caseName
if c.query == nil && c.jsonBody == nil && c.formBody == nil {
c.applyCaseTemplate(c.api.app.findCaseTemplate(c.api.path, caseName))
}
return c
}
func (c *ApiCase) applyCaseTemplate(tmpl *caseTemplate) {
if tmpl == nil {
return
}
if c.query == nil && tmpl.Query != nil {
c.query = tmpl.Query
}
if c.jsonBody == nil && tmpl.Json != nil {
c.jsonBody = tmpl.Json
}
if c.formBody == nil && tmpl.Form != nil {
c.formBody = tmpl.Form
}
if c.note == "" && tmpl.Note != "" {
c.note = tmpl.Note
}
}
// JSON 叠加 JSON body
@@ -294,15 +358,22 @@ func (c *ApiCase) execute(method, desc string, expectStatus int, expectArgs ...i
defer func() {
record := TestRecord{
Path: c.api.path,
Desc: desc,
CaseName: desc,
Passed: passed,
Duration: time.Since(start),
Method: method,
Note: c.note,
VerifyError: verifyError,
FailReason: failReason,
Path: c.api.path,
Desc: desc,
CaseName: desc,
Passed: passed,
Duration: time.Since(start),
Method: method,
Note: c.note,
VerifyError: verifyError,
FailReason: failReason,
FlowName: c.api.flowName,
FlowGroup: c.api.flowGroup,
FlowDesc: c.api.flowDesc,
FlowPrep: c.api.flowPrep,
FlowStep: c.api.flowStep,
FlowStepIndex: c.api.flowStepIndex,
BindCase: c.bindCase,
}
if c.query != nil {
record.Query = c.query
@@ -336,6 +407,9 @@ func (c *ApiCase) execute(method, desc string, expectStatus int, expectArgs ...i
}
record.ExpectResult = expectResp
c.api.app.collector.Add(record)
if c.bindCase != "" && c.api.flowName != "" {
c.api.app.syncBoundCaseToEndpoint(record)
}
}()
c.api.app.Db.ResetTestSQLErrorCount()
@@ -390,6 +464,9 @@ func (c *ApiCase) execute(method, desc string, expectStatus int, expectArgs ...i
}
c.api.lastResp = resp
if c.api.flowParent != nil {
c.api.flowParent.SetLastResp(resp)
}
if passed && c.verifyFn != nil {
if err := c.verifyFn(c.api); err != nil {
t.Errorf("Verify 校验失败: %s", err.Error())