@epoint-testtech/ep-stage-skill 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/codex-skill/ep-stage/code-list-generate/SKILL.md +168 -0
  2. package/codex-skill/ep-stage/code-list-generate/assets/code_list.template.json5 +325 -0
  3. package/codex-skill/ep-stage/code-list-generate/assets/example-zwplace.json5 +588 -0
  4. package/codex-skill/ep-stage/code-list-generate/evals/evals.json +47 -0
  5. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceAddAction.java +119 -0
  6. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceDetailAction.java +70 -0
  7. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceEditAction.java +107 -0
  8. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceListAction.java +167 -0
  9. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/api/ISchemeResourceService.java +84 -0
  10. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/api/entity/SchemeResource.java +218 -0
  11. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/impl/SchemeResourceService.java +146 -0
  12. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/impl/SchemeResourceServiceImpl.java +110 -0
  13. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourceadd.html +158 -0
  14. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourcedetail.html +89 -0
  15. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourceedit.html +125 -0
  16. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourcelist.html +224 -0
  17. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/docs//345/234/272/346/211/200/347/252/227/345/217/243/344/277/241/346/201/257/347/256/241/347/220/206/345/217/212/345/220/204/347/253/257/347/273/237/350/256/241/346/243/200/347/264/242/346/237/245/347/234/213_prd.md +602 -0
  18. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceAddAction.java +210 -0
  19. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceDetailAction.java +69 -0
  20. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceEditAction.java +235 -0
  21. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java +145 -0
  22. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/IGxhZwPlaceService.java +87 -0
  23. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/entity/GxhZwPlace.java +306 -0
  24. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/entity/GxhZwPlaceHistory.java +205 -0
  25. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/entity/GxhZwWindow.java +184 -0
  26. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/impl/GxhZwPlaceService.java +151 -0
  27. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/impl/GxhZwPlaceServiceImpl.java +96 -0
  28. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplaceadd.html +253 -0
  29. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplacedetail.html +146 -0
  30. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplaceedit.html +275 -0
  31. package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplacelist.html +168 -0
  32. package/codex-skill/ep-stage/code-list-generate/evals/grade-code-list.mjs +184 -0
  33. package/codex-skill/ep-stage/code-list-generate/references/epoint-extraction.md +106 -0
  34. package/codex-skill/ep-stage/code-list-generate/references/field-contract.md +122 -0
  35. package/codex-skill/ep-stage/code-list-generate/scripts/validate-code-list.mjs +202 -0
  36. package/codex-skill/ep-stage/glue-create-project/SKILL.md +227 -223
  37. package/dist/src/cli/index.js +0 -0
  38. package/dist/src/extractors/code-list-json5.d.ts +5 -0
  39. package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
  40. package/dist/src/generators/stage-skeleton-script.d.ts +4 -0
  41. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  42. package/dist/src/generators/stage-skeleton-script.js +389 -362
  43. package/dist/src/testcase/contract-testcase-assembler.d.ts +10 -1
  44. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
  45. package/dist/src/testcase/contract-testcase-assembler.js +80 -21
  46. package/package.json +14 -12
@@ -43,6 +43,12 @@ export type CrudCaseStage = {
43
43
  actionKind: GlueActionKind;
44
44
  queryKeyLabel: string;
45
45
  component: 'input' | 'listbox';
46
+ /**
47
+ * 条件性删除(如"草稿可删、在用不可删")时的删除用例变体:
48
+ * draft_success = 满足可删条件(草稿)删除成功;blocked = 不满足条件(在用)被业务规则阻断。
49
+ * 仅 actionKind='delete' 且条件性规则时出现;无条件阻断/正常可删不设置(保持原有用例形态)。
50
+ */
51
+ deleteVariant?: 'draft_success' | 'blocked';
46
52
  };
47
53
  /**
48
54
  * 按查询字段推导 CRUD 用例阶段序列。
@@ -50,15 +56,18 @@ export type CrudCaseStage = {
50
56
  * 规则:在新增表单中出现的查询字段(可写)产出完整 create/read/update/delete 四条;
51
57
  * 不在表单中的字段(如系统管理的状态)只产出 read 一条。
52
58
  * export 不在此计划内,由装配器单独追加。
59
+ * 条件性删除规则(conditionalDelete=true)时,可写键的 delete 由一条变两条:
60
+ * 草稿删除成功(draft_success)在前、在用删除阻断(blocked)在后。
53
61
  *
54
62
  * @param searchFields - 列表页查询字段(有序)。
55
63
  * @param formFieldLabels - 新增表单字段标签集合。
64
+ * @param conditionalDelete - 删除是否为条件性(businessRules 的 delete 规则带 deletableCondition)。
56
65
  * @returns 有序阶段计划,顺序与 buildGlueCases 产出的用例顺序一一对应。
57
66
  */
58
67
  export declare function planCrudCaseStages(searchFields: Array<{
59
68
  label: string;
60
69
  component: 'input' | 'listbox';
61
- }>, formFieldLabels: string[]): CrudCaseStage[];
70
+ }>, formFieldLabels: string[], conditionalDelete?: boolean): CrudCaseStage[];
62
71
  /**
63
72
  * 装配 contract + testcase + inference-trace 账本。
64
73
  *
@@ -1 +1 @@
1
- {"version":3,"file":"contract-testcase-assembler.d.ts","sourceRoot":"","sources":["../../../src/testcase/contract-testcase-assembler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAA2E,MAAM,kCAAkC,CAAC;AACxJ,OAAO,KAAK,EAAE,sBAAsB,EAAqB,MAAM,kCAAkC,CAAC;AAElG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AASrE,oBAAoB;AACpB,MAAM,MAAM,aAAa,GAAG;IAC1B,iCAAiC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC,CAAC;AAkZF,mBAAmB;AACnB,KAAK,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,CAAC,EACtE,eAAe,EAAE,MAAM,EAAE,GACxB,aAAa,EAAE,CAejB;AAiZD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAiKrF"}
1
+ {"version":3,"file":"contract-testcase-assembler.d.ts","sourceRoot":"","sources":["../../../src/testcase/contract-testcase-assembler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAA2E,MAAM,kCAAkC,CAAC;AACxJ,OAAO,KAAK,EAAE,sBAAsB,EAAqB,MAAM,kCAAkC,CAAC;AAElG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AASrE,oBAAoB;AACpB,MAAM,MAAM,aAAa,GAAG;IAC1B,iCAAiC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC,CAAC;AAwZF,mBAAmB;AACnB,KAAK,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,CAAC,EACtE,eAAe,EAAE,MAAM,EAAE,EACzB,iBAAiB,UAAQ,GACxB,aAAa,EAAE,CAsBjB;AAmbD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CA6LrF"}
@@ -49,7 +49,7 @@ const DEFAULT_LOCATORS = {
49
49
  * @param listPage - code_list.pages[role=list]。
50
50
  * @returns 覆盖 StageSkeletonCrudFlowSlots.locators 全部字段的定位表达式。
51
51
  */
52
- function buildCrudLocators(listPage) {
52
+ function buildCrudLocators(listPage, addOrEditPage, conditionalDelete = false) {
53
53
  const gridId = 'datagrid';
54
54
  const createLabel = listPage.buttons?.find((b) => b.action === 'create')?.label ?? '新增';
55
55
  const deleteLabel = listPage.buttons?.find((b) => b.action === 'delete')?.label ?? '删除选定';
@@ -59,6 +59,10 @@ function buildCrudLocators(listPage) {
59
59
  })?.label ?? '修改';
60
60
  const miniButton = (label) => `xpath=//a[contains(@class,'mini-button')][normalize-space()='${label}']`;
61
61
  const gridCell = `xpath=//*[@id='${gridId}']//div[text()='{{value}}']`;
62
+ // 条件性删除需要「草稿删除成功」用例:新增页保存草稿按钮(action=save,如「保存」)
63
+ // 与关闭按钮(action=cancel,草稿保存后弹窗不关闭,需显式关闭回列表页)。
64
+ const draftSaveLabel = addOrEditPage?.buttons?.find((b) => b.action === 'save')?.label;
65
+ const dialogCloseLabel = addOrEditPage?.buttons?.find((b) => b.action === 'cancel')?.label;
62
66
  return {
63
67
  createButton: miniButton(createLabel),
64
68
  addAutofillTrigger: `xpath=//*[@title='默认填充']`,
@@ -70,6 +74,8 @@ function buildCrudLocators(listPage) {
70
74
  listRowSelectLocatorTemplate: gridCell,
71
75
  listDeleteButton: miniButton(deleteLabel),
72
76
  deleteConfirmButton: 'text=确定',
77
+ ...(conditionalDelete && draftSaveLabel ? { addDraftSaveButton: miniButton(draftSaveLabel) } : {}),
78
+ ...(conditionalDelete && dialogCloseLabel ? { addDialogCloseButton: miniButton(dialogCloseLabel) } : {}),
73
79
  };
74
80
  }
75
81
  /**
@@ -365,16 +371,25 @@ function normalizeQueryComponent(fieldInfo) {
365
371
  * 规则:在新增表单中出现的查询字段(可写)产出完整 create/read/update/delete 四条;
366
372
  * 不在表单中的字段(如系统管理的状态)只产出 read 一条。
367
373
  * export 不在此计划内,由装配器单独追加。
374
+ * 条件性删除规则(conditionalDelete=true)时,可写键的 delete 由一条变两条:
375
+ * 草稿删除成功(draft_success)在前、在用删除阻断(blocked)在后。
368
376
  *
369
377
  * @param searchFields - 列表页查询字段(有序)。
370
378
  * @param formFieldLabels - 新增表单字段标签集合。
379
+ * @param conditionalDelete - 删除是否为条件性(businessRules 的 delete 规则带 deletableCondition)。
371
380
  * @returns 有序阶段计划,顺序与 buildGlueCases 产出的用例顺序一一对应。
372
381
  */
373
- export function planCrudCaseStages(searchFields, formFieldLabels) {
382
+ export function planCrudCaseStages(searchFields, formFieldLabels, conditionalDelete = false) {
374
383
  const stages = [];
375
384
  for (const field of searchFields) {
376
385
  if (formFieldLabels.includes(field.label)) {
377
- stages.push({ actionKind: 'create', queryKeyLabel: field.label, component: field.component }, { actionKind: 'read', queryKeyLabel: field.label, component: field.component }, { actionKind: 'update', queryKeyLabel: field.label, component: field.component }, { actionKind: 'delete', queryKeyLabel: field.label, component: field.component });
386
+ stages.push({ actionKind: 'create', queryKeyLabel: field.label, component: field.component }, { actionKind: 'read', queryKeyLabel: field.label, component: field.component }, { actionKind: 'update', queryKeyLabel: field.label, component: field.component });
387
+ if (conditionalDelete) {
388
+ stages.push({ actionKind: 'delete', queryKeyLabel: field.label, component: field.component, deleteVariant: 'draft_success' }, { actionKind: 'delete', queryKeyLabel: field.label, component: field.component, deleteVariant: 'blocked' });
389
+ }
390
+ else {
391
+ stages.push({ actionKind: 'delete', queryKeyLabel: field.label, component: field.component });
392
+ }
378
393
  }
379
394
  else {
380
395
  stages.push({ actionKind: 'read', queryKeyLabel: field.label, component: field.component });
@@ -421,9 +436,11 @@ function pickQueryKeyLabel(label, searchFieldLabels, dataKeyLabel) {
421
436
  * @param queryKey - 当前用例查询主键。
422
437
  * @param searchFieldLabels - 列表页查询字段标签集合。
423
438
  * @param dataKeyLabel - dataKey 对应的中文标签。
439
+ * @param deleteOutcome - 删除策略(与 skeletonSlots.assertions.deletePolicy 同源),
440
+ * 仅 delete 用例消费;缺省按 success_delete 出文案。
424
441
  * @returns testcase 执行字段。
425
442
  */
426
- function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, queryKeyInForm, searchFieldLabels, dataKeyLabel) {
443
+ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, queryKeyInForm, searchFieldLabels, dataKeyLabel, deleteOutcome, deleteVariant) {
427
444
  const queryKey = actionKind === 'export'
428
445
  ? pickQueryKeyLabel(dataKeyLabel ?? searchFieldLabels[0] ?? '不适用', searchFieldLabels, dataKeyLabel)
429
446
  : pickQueryKeyLabel(queryKeyLabel, searchFieldLabels, dataKeyLabel);
@@ -483,15 +500,37 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
483
500
  };
484
501
  }
485
502
  if (actionKind === 'delete') {
503
+ const deleteSteps = [
504
+ `1. 在列表页按${queryKey}搜索前置数据。`,
505
+ '2. 勾选搜索结果中的目标记录。',
506
+ '3. 点击{{删除按钮}}。',
507
+ '4. 在确认弹窗中点击{{确认按钮}}。',
508
+ ].join(' ');
509
+ // 断言文案必须与 skeletonSlots.assertions.deletePolicy 同源:
510
+ // 业务规则阻断删除时若仍写「列表应无匹配记录」,用例描述会与脚本实际执行(断言阻断文案)相悖。
511
+ // 条件性删除(如"草稿可删、在用不可删")按 deleteVariant 分叉:
512
+ // draft_success = 草稿数据删除成功;blocked = 在用数据删除被阻断。
513
+ if (deleteVariant === 'draft_success') {
514
+ return {
515
+ queryKey,
516
+ precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增(仅保存,数据为草稿状态)。`,
517
+ steps: deleteSteps,
518
+ assertion: `删除后再次按${queryKey}搜索该数据,列表应无匹配记录。`,
519
+ };
520
+ }
521
+ if (deleteVariant === 'blocked' || deleteOutcome?.deletePolicy === 'blocked_by_business_rule') {
522
+ const blockMessage = deleteOutcome?.blockedDeleteMessage ?? '不允许删除';
523
+ return {
524
+ queryKey,
525
+ precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增(保存并关闭,数据为在用状态)。`,
526
+ steps: deleteSteps,
527
+ assertion: `系统应阻断删除并提示『${blockMessage}』;再次按${queryKey}搜索该数据,记录仍存在。`,
528
+ };
529
+ }
486
530
  return {
487
531
  queryKey,
488
532
  precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增。`,
489
- steps: [
490
- `1. 在列表页按${queryKey}搜索前置数据。`,
491
- '2. 勾选搜索结果中的目标记录。',
492
- '3. 点击{{删除按钮}}。',
493
- '4. 在确认弹窗中点击{{确认按钮}}。',
494
- ].join(' '),
533
+ steps: deleteSteps,
495
534
  assertion: `删除后再次按${queryKey}搜索该数据,列表应无匹配记录。`,
496
535
  };
497
536
  }
@@ -515,23 +554,25 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
515
554
  * @param dataKeyLabel - dataKey 对应的中文标签。
516
555
  * @returns cases 数组(generationStrategy 全 glue)。
517
556
  */
518
- function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dataKeyLabel) {
557
+ function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dataKeyLabel, deleteOutcome, conditionalDelete = false) {
519
558
  const searchFieldLabels = searchFields.map((fieldInfo) => fieldInfo.label);
520
559
  const componentForLabel = (label) => searchFields.find((fieldInfo) => fieldInfo.label === label)?.component ?? 'input';
521
560
  const queryKeyInForm = (label) => formFieldLabels.includes(label);
522
- const mk = (actionKind, queryKeyLabel, coverageStatus) => ({
561
+ // 条件性删除的标题后缀:spec 生成器按此后缀识别删除变体(parseCrudCaseTitle 同步解析)。
562
+ const deleteTitleSuffix = (variant) => variant === 'draft_success' ? '-草稿' : variant === 'blocked' ? '-在用' : '';
563
+ const mk = (actionKind, queryKeyLabel, coverageStatus, deleteVariant) => ({
523
564
  caseId: nextCaseId(sequence),
524
565
  title: actionKind === 'export'
525
566
  ? `${moduleName} export`
526
- : `${moduleName} ${actionKind}(${queryKeyLabel})`,
567
+ : `${moduleName} ${actionKind}(${queryKeyLabel}${deleteTitleSuffix(deleteVariant)})`,
527
568
  generationStrategy: 'glue',
528
569
  coverageStatus,
529
570
  reviewStatus: 'confirmed',
530
- ...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel),
571
+ ...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel, deleteOutcome, deleteVariant),
531
572
  });
532
573
  // 计划驱动:保证用例条数/顺序与 spec 生成器逐条渲染的 test 一一对应。
533
574
  // listbox 键的 read 已由骨架强制断言行锚点命中,不再是 partial。
534
- const cases = planCrudCaseStages(searchFields, formFieldLabels).map((stage) => mk(stage.actionKind, stage.queryKeyLabel, 'covered'));
575
+ const cases = planCrudCaseStages(searchFields, formFieldLabels, conditionalDelete).map((stage) => mk(stage.actionKind, stage.queryKeyLabel, 'covered', stage.deleteVariant));
535
576
  cases.push(mk('export', dataKeyLabel ?? searchFieldLabels[0] ?? '不适用', 'covered'));
536
577
  return cases;
537
578
  }
@@ -706,20 +747,35 @@ export function assembleJson5ContractAndTestcase(input) {
706
747
  // 从 code_list.businessRules 里找 scope 含 'delete' 的规则(如 delete.onlyDraftAllowed
707
748
  // "非 DRAFT 状态删除时被阻断"),命中则映射为 blocked_by_business_rule;否则 success_delete。
708
749
  // assertionHint 直接作为 skeleton 的阻断消息断言值。
709
- const deleteRule = (codeList.businessRules ?? []).find((r) => {
750
+ const deleteScopedRules = (codeList.businessRules ?? []).filter((r) => {
710
751
  const anyRule = r;
711
752
  return Array.isArray(anyRule.scope) && anyRule.scope.includes('delete');
712
753
  });
754
+ // 自愈加固:delete scope 可能混入「未勾选拦截」等操作前置规则(语义上不是记录级删除阻断),
755
+ // 不能简单取第一条。优先级:assertionHint 含阻断语义关键词(不可删除/不允许删除等)者 >
756
+ // 带 assertionHint 者 > 第一条。上游 code-list 规范仍要求阻断规则排第一,此处只是兜底。
757
+ const BLOCK_HINT_PATTERN = /不可删除|不允许删除|无法删除|不能删除|禁止删除/;
758
+ const readHint = (r) => {
759
+ const hint = r.assertionHint
760
+ ?? r.assertion;
761
+ return typeof hint === 'string' && hint.trim().length > 0 ? hint : undefined;
762
+ };
763
+ const deleteRule = deleteScopedRules.find((r) => BLOCK_HINT_PATTERN.test(readHint(r) ?? ''))
764
+ ?? deleteScopedRules.find((r) => readHint(r) !== undefined)
765
+ ?? deleteScopedRules[0];
713
766
  const deletePolicy = deleteRule ? 'blocked_by_business_rule' : 'success_delete';
714
- const blockedDeleteMessage = deleteRule
715
- ? (deleteRule.assertionHint
716
- ?? deleteRule.assertion)
767
+ const blockedDeleteMessage = deleteRule ? readHint(deleteRule) : undefined;
768
+ // 条件性删除(如"草稿可删、在用不可删"):规则带 deletableCondition 时,
769
+ // 每个可写查询主键产出两类删除用例(草稿删除成功 + 在用删除阻断)。
770
+ const deletableCondition = deleteRule
771
+ ? deleteRule.deletableCondition
717
772
  : undefined;
773
+ const conditionalDelete = Boolean(deleteRule) && typeof deletableCondition === 'string' && deletableCondition.trim().length > 0;
718
774
  const skeletonSlotsCrud = {
719
775
  moduleId: module,
720
776
  searchConditions: buildSearchConditionSlots(codeList, listPage, addOrEditPage),
721
777
  frames: resolveFrameKeywords(codeList),
722
- locators: buildCrudLocators(listPage),
778
+ locators: buildCrudLocators(listPage, addOrEditPage, conditionalDelete),
723
779
  assertions: {
724
780
  deletePolicy,
725
781
  blockedDeleteMessage,
@@ -765,7 +821,10 @@ export function assembleJson5ContractAndTestcase(input) {
765
821
  unresolvedSlots: [], // 框架默认策略都已确认,不进 unresolvedSlots
766
822
  };
767
823
  const caseIdSequence = { pageIndex: 1, nextCaseIndex: 1 };
768
- const cases = buildGlueCases(moduleName, searchFields, formFieldLabels, caseIdSequence, dataKeyLabel);
824
+ const cases = buildGlueCases(moduleName, searchFields, formFieldLabels, caseIdSequence, dataKeyLabel, {
825
+ deletePolicy,
826
+ blockedDeleteMessage,
827
+ }, conditionalDelete);
769
828
  const childGridCases = childGridSlots ? buildChildGridCases(childGridSlots, firstRole, caseIdSequence) : [];
770
829
  cases.push(...childGridCases);
771
830
  const buttonInferences = inferButtonInteractions(codeList, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoint-testtech/ep-stage-skill",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -19,6 +19,18 @@
19
19
  "registry": "https://registry.npmjs.org/"
20
20
  },
21
21
  "description": "Agent skills and TypeScript tools for ep-stage glue-code workflows.",
22
+ "scripts": {
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "vitest run",
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "prepare": "pnpm run build",
27
+ "prepack": "pnpm run build",
28
+ "generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
29
+ "generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
30
+ "run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
31
+ "cli": "node --import tsx src/cli/index.ts",
32
+ "generate:testcase": "node --import tsx src/cli/index.ts testcase"
33
+ },
22
34
  "dependencies": {
23
35
  "cheerio": "^1.0.0",
24
36
  "json5": "^2.2.3",
@@ -29,15 +41,5 @@
29
41
  "tsx": "^4.20.0",
30
42
  "typescript": "^6.0.3",
31
43
  "vitest": "^4.0.0"
32
- },
33
- "scripts": {
34
- "typecheck": "tsc --noEmit",
35
- "test": "vitest run",
36
- "build": "tsc -p tsconfig.build.json",
37
- "generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
38
- "generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
39
- "run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
40
- "cli": "node --import tsx src/cli/index.ts",
41
- "generate:testcase": "node --import tsx src/cli/index.ts testcase"
42
44
  }
43
- }
45
+ }