feat(testing): Flows/FromCase、增量 swagger 与 Doc-Driven 门禁
补齐多接口流程联调与控制台体验,并落地仓内 Doc-Driven+TDD 总规则与文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+103
-26
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user