@epoint-testtech/ep-stage-skill 0.0.9 → 0.5.0-alpha.2

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 (158) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -223
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
  4. package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
  5. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -95
  6. package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
  7. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
  8. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
  9. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
  10. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  11. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +127 -166
  12. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  13. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  14. package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
  15. package/dist/src/cli/generate-testcase.d.ts +0 -1
  16. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  17. package/dist/src/cli/generate-testcase.js +1 -6
  18. package/dist/src/cli/index.d.ts.map +1 -1
  19. package/dist/src/cli/index.js +14 -38
  20. package/dist/src/cli/run.d.ts +0 -2
  21. package/dist/src/cli/run.d.ts.map +1 -1
  22. package/dist/src/cli/run.js +21 -70
  23. package/dist/src/cli/spec.d.ts.map +1 -1
  24. package/dist/src/cli/spec.js +0 -9
  25. package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
  26. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
  27. package/dist/src/extractors/code-list-json5.d.ts +1 -34
  28. package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
  29. package/dist/src/extractors/code-list-json5.js +0 -1
  30. package/dist/src/extractors/html-page.d.ts +0 -9
  31. package/dist/src/extractors/html-page.d.ts.map +1 -1
  32. package/dist/src/extractors/html-page.js +2 -26
  33. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  34. package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
  35. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  36. package/dist/src/generators/stage-skeleton-script.js +116 -449
  37. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
  38. package/dist/src/runtime/ai-runtime-trace.js +0 -8
  39. package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
  40. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
  41. package/dist/src/testcase/contract-testcase-assembler.js +75 -329
  42. package/dist/src/validation/contract.d.ts.map +1 -1
  43. package/dist/src/validation/contract.js +0 -29
  44. package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
  45. package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
  46. package/dist/test/cli/generate-testcase-json5.test.js +154 -0
  47. package/dist/test/cli/index.test.d.ts +2 -0
  48. package/dist/test/cli/index.test.d.ts.map +1 -0
  49. package/dist/test/cli/index.test.js +114 -0
  50. package/dist/test/cli/run.test.d.ts +2 -0
  51. package/dist/test/cli/run.test.d.ts.map +1 -0
  52. package/dist/test/cli/run.test.js +429 -0
  53. package/dist/test/cli/spec.test.d.ts +2 -0
  54. package/dist/test/cli/spec.test.d.ts.map +1 -0
  55. package/dist/test/cli/spec.test.js +112 -0
  56. package/dist/test/code-list-json5.test.d.ts +2 -0
  57. package/dist/test/code-list-json5.test.d.ts.map +1 -0
  58. package/dist/test/code-list-json5.test.js +84 -0
  59. package/dist/test/code-list.test.d.ts +2 -0
  60. package/dist/test/code-list.test.d.ts.map +1 -0
  61. package/dist/test/code-list.test.js +39 -0
  62. package/dist/test/gap-inference.test.d.ts +2 -0
  63. package/dist/test/gap-inference.test.d.ts.map +1 -0
  64. package/dist/test/gap-inference.test.js +597 -0
  65. package/dist/test/generalization.test.d.ts +2 -0
  66. package/dist/test/generalization.test.d.ts.map +1 -0
  67. package/dist/test/generalization.test.js +73 -0
  68. package/dist/test/material-inventory.test.d.ts +2 -0
  69. package/dist/test/material-inventory.test.d.ts.map +1 -0
  70. package/dist/test/material-inventory.test.js +155 -0
  71. package/dist/test/observable-chain.test.d.ts +2 -0
  72. package/dist/test/observable-chain.test.d.ts.map +1 -0
  73. package/dist/test/observable-chain.test.js +123 -0
  74. package/dist/test/page-structure.test.d.ts +2 -0
  75. package/dist/test/page-structure.test.d.ts.map +1 -0
  76. package/dist/test/page-structure.test.js +45 -0
  77. package/dist/test/runtime/ai-runtime-trace.test.d.ts +2 -0
  78. package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
  79. package/dist/test/runtime/ai-runtime-trace.test.js +78 -0
  80. package/dist/test/scenario-inference.test.d.ts +2 -0
  81. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  82. package/dist/test/scenario-inference.test.js +73 -0
  83. package/dist/test/stage-context.test.d.ts +2 -0
  84. package/dist/test/stage-context.test.d.ts.map +1 -0
  85. package/dist/test/stage-context.test.js +395 -0
  86. package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
  87. package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
  88. package/dist/test/testcase/testcase-markdown.test.js +48 -0
  89. package/dist/test/testcase-generator.test.d.ts +2 -0
  90. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  91. package/dist/test/testcase-generator.test.js +276 -0
  92. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  93. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  94. package/dist/test/testcase-skeleton.test.js +185 -0
  95. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  96. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  97. package/dist/test/testcase-spec-assembly.test.js +105 -0
  98. package/dist/test/util/credentials.test.d.ts +2 -0
  99. package/dist/test/util/credentials.test.d.ts.map +1 -0
  100. package/dist/test/util/credentials.test.js +64 -0
  101. package/dist/test/util/i18n-testcase.test.d.ts +2 -0
  102. package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
  103. package/dist/test/util/i18n-testcase.test.js +119 -0
  104. package/dist/test/util/softlink.test.d.ts +2 -0
  105. package/dist/test/util/softlink.test.d.ts.map +1 -0
  106. package/dist/test/util/softlink.test.js +82 -0
  107. package/dist/test/validation/contract.test.d.ts +2 -0
  108. package/dist/test/validation/contract.test.d.ts.map +1 -0
  109. package/dist/test/validation/contract.test.js +116 -0
  110. package/dist/test/validation/credentials.test.d.ts +2 -0
  111. package/dist/test/validation/credentials.test.d.ts.map +1 -0
  112. package/dist/test/validation/credentials.test.js +74 -0
  113. package/dist/test/validation/projects-index.test.d.ts +2 -0
  114. package/dist/test/validation/projects-index.test.d.ts.map +1 -0
  115. package/dist/test/validation/projects-index.test.js +48 -0
  116. package/dist/test/validation/testcase-v1-draft.test.d.ts +2 -0
  117. package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
  118. package/dist/test/validation/testcase-v1-draft.test.js +173 -0
  119. package/dist/vitest.config.d.ts +3 -0
  120. package/dist/vitest.config.d.ts.map +1 -0
  121. package/dist/vitest.config.js +7 -0
  122. package/docs/README.md +124 -124
  123. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  124. package/examples/schemeresource.module-hints.json +38 -38
  125. package/examples/schemeresource.observable.code_list.md +37 -37
  126. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  127. package/examples/zwplace-placecategory-validation.json +29 -29
  128. package/examples/zwplace.module-hints.json +69 -69
  129. package/examples/zwplace.observable.code_list.md +37 -37
  130. package/package.json +19 -13
  131. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
  132. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
  133. package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
  134. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
  135. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
  136. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
  137. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
  138. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
  139. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
  140. package/dist/src/cli/probe.d.ts +0 -46
  141. package/dist/src/cli/probe.d.ts.map +0 -1
  142. package/dist/src/cli/probe.js +0 -447
  143. package/dist/src/cli/run-report.d.ts +0 -46
  144. package/dist/src/cli/run-report.d.ts.map +0 -1
  145. package/dist/src/cli/run-report.js +0 -316
  146. package/dist/src/extractors/prd-extractor.d.ts +0 -70
  147. package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
  148. package/dist/src/extractors/prd-extractor.js +0 -248
  149. package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
  150. package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
  151. package/dist/src/testcase/button-interaction-inference.js +0 -385
  152. package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
  153. package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
  154. package/dist/src/testcase/prd-button-mapper.js +0 -148
  155. package/dist/src/testcase/testcase-v2.d.ts +0 -50
  156. package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
  157. package/dist/src/testcase/testcase-v2.js +0 -1
  158. package/docs/mvp-usage-guide.md +0 -298
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { i18nTestcase } from '../../src/util/i18n-testcase.js';
3
+ /**
4
+ * REQ-UX-03:testcase.json5 英文字段名 / 状态值 → testcase.md 中文显示文案。
5
+ * 映射表见 spec §4.6。CLI 写 testcase.md 时调用本 util;testcase.json5 内部字段名永远英文。
6
+ *
7
+ * 说明:`unresolved` 在 spec 表中同时出现于「字段名」(未确认项) 和「状态值」(未解决),
8
+ * 这是已知冲突;当前实现以扁平 lookup 表为准(后者覆盖前者),见 i18nTestcase 注释。
9
+ */
10
+ describe('i18nTestcase', () => {
11
+ describe('审阅状态值', () => {
12
+ it('confirmed → 已确认', () => {
13
+ expect(i18nTestcase('confirmed')).toBe('已确认');
14
+ });
15
+ it('needs_review → 待审阅', () => {
16
+ expect(i18nTestcase('needs_review')).toBe('待审阅');
17
+ });
18
+ it('blocked → 已阻断', () => {
19
+ expect(i18nTestcase('blocked')).toBe('已阻断');
20
+ });
21
+ it('planned → 已计划', () => {
22
+ expect(i18nTestcase('planned')).toBe('已计划');
23
+ });
24
+ it('candidate → 候选', () => {
25
+ expect(i18nTestcase('candidate')).toBe('候选');
26
+ });
27
+ it('resolved → 已解决', () => {
28
+ expect(i18nTestcase('resolved')).toBe('已解决');
29
+ });
30
+ });
31
+ describe('推荐动作值', () => {
32
+ it('infer → 推理', () => {
33
+ expect(i18nTestcase('infer')).toBe('推理');
34
+ });
35
+ it('supplement-hints → 补充 hints', () => {
36
+ expect(i18nTestcase('supplement-hints')).toBe('补充 hints');
37
+ });
38
+ it('skip → 跳过', () => {
39
+ expect(i18nTestcase('skip')).toBe('跳过');
40
+ });
41
+ });
42
+ describe('场景值', () => {
43
+ it('crud.single-page → 单页 CRUD', () => {
44
+ expect(i18nTestcase('crud.single-page')).toBe('单页 CRUD');
45
+ });
46
+ it('crud.nested → 嵌套 CRUD', () => {
47
+ expect(i18nTestcase('crud.nested')).toBe('嵌套 CRUD');
48
+ });
49
+ it('statistic → 统计视图', () => {
50
+ expect(i18nTestcase('statistic')).toBe('统计视图');
51
+ });
52
+ it('approval → 审批流', () => {
53
+ expect(i18nTestcase('approval')).toBe('审批流');
54
+ });
55
+ });
56
+ describe('字段名', () => {
57
+ it('caseId → 用例 ID', () => {
58
+ expect(i18nTestcase('caseId')).toBe('用例 ID');
59
+ });
60
+ it('actionId → 动作 ID', () => {
61
+ expect(i18nTestcase('actionId')).toBe('动作 ID');
62
+ });
63
+ it('scenario → 场景类型', () => {
64
+ expect(i18nTestcase('scenario')).toBe('场景类型');
65
+ });
66
+ it('menuPath → 菜单路径', () => {
67
+ expect(i18nTestcase('menuPath')).toBe('菜单路径');
68
+ });
69
+ it('requiredRole → 操作角色', () => {
70
+ expect(i18nTestcase('requiredRole')).toBe('操作角色');
71
+ });
72
+ it('requiredSystems → 需求系统集', () => {
73
+ expect(i18nTestcase('requiredSystems')).toBe('需求系统集');
74
+ });
75
+ it('requiredRoles → 需求角色集', () => {
76
+ expect(i18nTestcase('requiredRoles')).toBe('需求角色集');
77
+ });
78
+ it('coveredActions → 骨架已命中动作', () => {
79
+ expect(i18nTestcase('coveredActions')).toBe('骨架已命中动作');
80
+ });
81
+ it('uncoveredCandidates → 骨架未命中候选', () => {
82
+ expect(i18nTestcase('uncoveredCandidates')).toBe('骨架未命中候选');
83
+ });
84
+ it('businessIntent → 业务意图', () => {
85
+ expect(i18nTestcase('businessIntent')).toBe('业务意图');
86
+ });
87
+ it('assertionExpectation → 预期断言', () => {
88
+ expect(i18nTestcase('assertionExpectation')).toBe('预期断言');
89
+ });
90
+ it('recommendedAction → 推荐动作', () => {
91
+ expect(i18nTestcase('recommendedAction')).toBe('推荐动作');
92
+ });
93
+ it('reviewStatus → 审阅状态', () => {
94
+ expect(i18nTestcase('reviewStatus')).toBe('审阅状态');
95
+ });
96
+ it('evidence → 证据', () => {
97
+ expect(i18nTestcase('evidence')).toBe('证据');
98
+ });
99
+ });
100
+ describe('未知值兜底', () => {
101
+ it('未知值原样返回', () => {
102
+ expect(i18nTestcase('some-unknown-value')).toBe('some-unknown-value');
103
+ });
104
+ it('空字符串原样返回', () => {
105
+ expect(i18nTestcase('')).toBe('');
106
+ });
107
+ it('英文字段名未在表中时原样返回', () => {
108
+ expect(i18nTestcase('createdAt')).toBe('createdAt');
109
+ });
110
+ });
111
+ describe('unresolved 冲突(已知 trade-off)', () => {
112
+ // spec §4.6 中 `unresolved` 同时是字段名(未确认项)和状态值(未解决)。
113
+ // 扁平 lookup 表无法同时持有两个映射,后定义的状态值覆盖字段名。
114
+ // 字段名场景下 CLI 可在调用前对 key 做硬编码标签替换;此 util 保留单值兜底。
115
+ it('unresolved → 未解决(状态值优先)', () => {
116
+ expect(i18nTestcase('unresolved')).toBe('未解决');
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=softlink.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"softlink.test.d.ts","sourceRoot":"","sources":["../../../test/util/softlink.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdtempSync, rmSync, writeFileSync, existsSync, readFileSync, lstatSync, mkdirSync, symlinkSync } from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { createSoftlinkWithFallback, softlinkFs } from '../../src/util/softlink.js';
6
+ /**
7
+ * REQ-FILE-02:跨平台软链 fallback util
8
+ * 三档 fallback:symlink(macOS/Linux)→ hardlink → copy + 时间戳警示(Windows 无管理员权限)。
9
+ *
10
+ * 注:fallback 路径通过 softlinkFs.* spy 模拟失败;ESM namespace 不可写,
11
+ * 因此 softlink.ts 显式暴露 softlinkFs 对象供测试替换。
12
+ */
13
+ describe('createSoftlinkWithFallback', () => {
14
+ let tmpDir;
15
+ let target;
16
+ let linkPath;
17
+ beforeEach(() => {
18
+ tmpDir = mkdtempSync(path.join(os.tmpdir(), 'softlink-test-'));
19
+ target = path.join(tmpDir, 'code_list.md');
20
+ linkPath = path.join(tmpDir, 'requirement', 'code_list.md');
21
+ writeFileSync(target, '# upstream code list\n');
22
+ mkdirSync(path.dirname(linkPath), { recursive: true });
23
+ });
24
+ afterEach(() => {
25
+ vi.restoreAllMocks();
26
+ rmSync(tmpDir, { recursive: true, force: true });
27
+ });
28
+ it('symlink 成功时返回 strategy=symlink,且链接指向 target', () => {
29
+ const result = createSoftlinkWithFallback(target, linkPath);
30
+ expect(result.strategy).toBe('symlink');
31
+ expect(result.warned).toBeUndefined();
32
+ expect(existsSync(linkPath)).toBe(true);
33
+ expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);
34
+ // 通过链接读到的内容应该和源文件一致
35
+ expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
36
+ });
37
+ it('目标已存在时先 unlink 再创建(覆盖语义)', () => {
38
+ // 先放一个老文件占位
39
+ writeFileSync(linkPath, '# stale content\n');
40
+ const result = createSoftlinkWithFallback(target, linkPath);
41
+ expect(result.strategy).toBe('symlink');
42
+ expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);
43
+ expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
44
+ });
45
+ it('目标已存在且为旧 symlink 时也能覆盖', () => {
46
+ // 先建一个指向别处的 symlink
47
+ const altTarget = path.join(tmpDir, 'alt.md');
48
+ writeFileSync(altTarget, '# alt\n');
49
+ symlinkSync(altTarget, linkPath, 'file');
50
+ expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);
51
+ const result = createSoftlinkWithFallback(target, linkPath);
52
+ expect(result.strategy).toBe('symlink');
53
+ expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
54
+ });
55
+ it('symlink 失败时回退到 hardlink', () => {
56
+ // mock softlinkFs.symlinkSync 抛错,模拟 Windows 无权限
57
+ vi.spyOn(softlinkFs, 'symlinkSync').mockImplementation(() => {
58
+ throw new Error('EPERM: operation not permitted');
59
+ });
60
+ const result = createSoftlinkWithFallback(target, linkPath);
61
+ expect(result.strategy).toBe('hardlink');
62
+ expect(result.warned).toBeUndefined();
63
+ expect(existsSync(linkPath)).toBe(true);
64
+ expect(lstatSync(linkPath).isSymbolicLink()).toBe(false);
65
+ expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
66
+ });
67
+ it('symlink 与 hardlink 均失败时回退到 copy,并返回 warned=true', () => {
68
+ vi.spyOn(softlinkFs, 'symlinkSync').mockImplementation(() => {
69
+ throw new Error('EPERM: symlink not permitted');
70
+ });
71
+ vi.spyOn(softlinkFs, 'linkSync').mockImplementation(() => {
72
+ throw new Error('EXDEV: cross-device link not permitted');
73
+ });
74
+ const result = createSoftlinkWithFallback(target, linkPath);
75
+ expect(result.strategy).toBe('copy');
76
+ expect(result.warned).toBe(true);
77
+ expect(existsSync(linkPath)).toBe(true);
78
+ expect(lstatSync(linkPath).isSymbolicLink()).toBe(false);
79
+ // copy 的文件内容等于源文件
80
+ expect(readFileSync(linkPath, 'utf8')).toBe('# upstream code list\n');
81
+ });
82
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.test.d.ts","sourceRoot":"","sources":["../../../test/validation/contract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateContract } from '../../src/validation/contract.js';
3
+ /**
4
+ * 构造一份满足 contract v1-draft schema 的最小合法契约对象。
5
+ *
6
+ * @returns 合法 contract v1-draft 对象。
7
+ */
8
+ function makeValidContract() {
9
+ return {
10
+ schemaVersion: 'ep-stage-contract/v1-draft',
11
+ contractId: 'CONTRACT-001',
12
+ contractKind: 'crud_export',
13
+ base: {
14
+ module: { id: 'org-management', label: '组织管理' },
15
+ navigation: { entryMode: 'menu', menuPath: '系统管理 > 组织管理' },
16
+ credentials: { source: '.env' },
17
+ },
18
+ scenario: {
19
+ kind: 'crud_export',
20
+ searchFields: [
21
+ { field: 'orgName', label: '组织名称', component: 'input' },
22
+ ],
23
+ skeletonSlots: {
24
+ crud: { moduleId: 'org-management', searchConditions: [], frames: [], locators: {}, assertions: [] },
25
+ export: { moduleId: 'org-management', label: '导出', mode: 'button' },
26
+ },
27
+ gapCandidates: [
28
+ { gapId: 'GAP-001', label: '批量删除', generationStrategy: 'ai' },
29
+ ],
30
+ },
31
+ unresolvedSlots: [],
32
+ };
33
+ }
34
+ describe('validateContract', () => {
35
+ it('合法 v1-draft contract 通过', () => {
36
+ const result = validateContract(makeValidContract());
37
+ expect(result.schemaVersion).toBe('ep-stage-contract/v1-draft');
38
+ expect(result.scenario.skeletonSlots.crud).toBeDefined();
39
+ expect(result.scenario.skeletonSlots.export).toBeDefined();
40
+ });
41
+ it('schemaVersion 非 ep-stage-contract/v1-draft 报错', () => {
42
+ const c = { ...makeValidContract(), schemaVersion: 'v2' };
43
+ expect(() => validateContract(c)).toThrow(/schemaVersion/);
44
+ });
45
+ it('缺 scenario.searchFields[] 报错', () => {
46
+ const c = makeValidContract();
47
+ const sc = c.scenario;
48
+ delete sc.searchFields;
49
+ expect(() => validateContract(c)).toThrow(/searchFields/);
50
+ });
51
+ it('searchFields 为空数组报错', () => {
52
+ const c = makeValidContract();
53
+ const sc = c.scenario;
54
+ sc.searchFields = [];
55
+ expect(() => validateContract(c)).toThrow(/searchFields/);
56
+ });
57
+ it('缺 scenario.skeletonSlots 报错', () => {
58
+ const c = makeValidContract();
59
+ const sc = c.scenario;
60
+ delete sc.skeletonSlots;
61
+ expect(() => validateContract(c)).toThrow(/skeletonSlots/);
62
+ });
63
+ it('缺 scenario.skeletonSlots.crud 报错', () => {
64
+ const c = makeValidContract();
65
+ const sc = c.scenario;
66
+ const slots = sc.skeletonSlots;
67
+ delete slots.crud;
68
+ expect(() => validateContract(c)).toThrow(/crud/);
69
+ });
70
+ it('缺 scenario.skeletonSlots.export 报错', () => {
71
+ const c = makeValidContract();
72
+ const sc = c.scenario;
73
+ const slots = sc.skeletonSlots;
74
+ delete slots.export;
75
+ expect(() => validateContract(c)).toThrow(/export/);
76
+ });
77
+ it('缺 unresolvedSlots[] 报错', () => {
78
+ const c = makeValidContract();
79
+ delete c.unresolvedSlots;
80
+ expect(() => validateContract(c)).toThrow(/unresolvedSlots/);
81
+ });
82
+ it('缺 base.module 报错', () => {
83
+ const c = makeValidContract();
84
+ const base = c.base;
85
+ delete base.module;
86
+ expect(() => validateContract(c)).toThrow(/base\.module/);
87
+ });
88
+ it('缺 base.navigation 报错', () => {
89
+ const c = makeValidContract();
90
+ const base = c.base;
91
+ delete base.navigation;
92
+ expect(() => validateContract(c)).toThrow(/base\.navigation/);
93
+ });
94
+ it('base.navigation.entryMode 非法值报错', () => {
95
+ const c = makeValidContract();
96
+ const base = c.base;
97
+ const nav = base.navigation;
98
+ nav.entryMode = 'unknown';
99
+ expect(() => validateContract(c)).toThrow(/entryMode/);
100
+ });
101
+ it('directUrl 模式缺 base.navigation.directUrl 报错', () => {
102
+ const c = makeValidContract();
103
+ const base = c.base;
104
+ const nav = base.navigation;
105
+ nav.entryMode = 'directUrl';
106
+ delete nav.menuPath;
107
+ expect(() => validateContract(c)).toThrow(/base\.navigation\.directUrl/);
108
+ });
109
+ it('gapCandidates[].generationStrategy 非法值报错', () => {
110
+ const c = makeValidContract();
111
+ const sc = c.scenario;
112
+ const gaps = sc.gapCandidates;
113
+ gaps[0].generationStrategy = 'unknown';
114
+ expect(() => validateContract(c)).toThrow(/generationStrategy/);
115
+ });
116
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=credentials.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.test.d.ts","sourceRoot":"","sources":["../../../test/validation/credentials.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateCredentials } from '../../src/validation/credentials.js';
3
+ describe('validateCredentials', () => {
4
+ it('合法数组(≥1 条)通过', () => {
5
+ const result = validateCredentials([
6
+ {
7
+ url: 'http://example.test',
8
+ username: 'admin',
9
+ password: 'pwd',
10
+ role: '系统管理员',
11
+ systemName: '基础平台',
12
+ },
13
+ ]);
14
+ expect(result).toHaveLength(1);
15
+ expect(result[0]).toMatchObject({
16
+ url: 'http://example.test',
17
+ loginUrl: 'http://example.test',
18
+ username: 'admin',
19
+ password: 'pwd',
20
+ role: '系统管理员',
21
+ systemName: '基础平台',
22
+ });
23
+ });
24
+ it('允许无 role / systemName 的条目', () => {
25
+ const result = validateCredentials([
26
+ { url: 'http://example.test', username: 'admin', password: 'pwd' },
27
+ ]);
28
+ expect(result[0]).toEqual({
29
+ url: 'http://example.test',
30
+ loginUrl: 'http://example.test',
31
+ username: 'admin',
32
+ password: 'pwd',
33
+ });
34
+ expect(result[0]).not.toHaveProperty('role');
35
+ expect(result[0]).not.toHaveProperty('systemName');
36
+ });
37
+ it('非数组报错', () => {
38
+ expect(() => validateCredentials({ url: 'x' })).toThrow(/数组/);
39
+ });
40
+ it('空数组报错', () => {
41
+ expect(() => validateCredentials([])).toThrow(/至少|1 条/);
42
+ });
43
+ it('条目缺 loginUrl/url 报错', () => {
44
+ expect(() => validateCredentials([{ username: 'admin', password: 'pwd' }])).toThrow(/loginUrl/);
45
+ });
46
+ it('条目缺 username 报错', () => {
47
+ expect(() => validateCredentials([{ url: 'http://example.test', password: 'pwd' }])).toThrow(/username/);
48
+ });
49
+ it('条目缺 password 报错', () => {
50
+ expect(() => validateCredentials([{ url: 'http://example.test', username: 'admin' }])).toThrow(/password/);
51
+ });
52
+ it('条目空字符串字段(空白)报错', () => {
53
+ expect(() => validateCredentials([{ url: ' ', username: 'admin', password: 'pwd' }])).toThrow(/loginUrl/);
54
+ });
55
+ it('同 (url, role) 重复报错', () => {
56
+ expect(() => validateCredentials([
57
+ { url: 'http://example.test', username: 'a', password: 'p1', role: '管理员' },
58
+ { url: 'http://example.test', username: 'b', password: 'p2', role: '管理员' },
59
+ ])).toThrow(/重复/);
60
+ });
61
+ it('同 url 但 role 不同允许', () => {
62
+ const result = validateCredentials([
63
+ { url: 'http://example.test', username: 'a', password: 'p1', role: '管理员' },
64
+ { url: 'http://example.test', username: 'b', password: 'p2', role: '业务员' },
65
+ ]);
66
+ expect(result).toHaveLength(2);
67
+ });
68
+ it('同 url 都没 role 视作 (url, "") 也会触发重复', () => {
69
+ expect(() => validateCredentials([
70
+ { url: 'http://example.test', username: 'a', password: 'p1' },
71
+ { url: 'http://example.test', username: 'b', password: 'p2' },
72
+ ])).toThrow(/重复/);
73
+ });
74
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=projects-index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects-index.test.d.ts","sourceRoot":"","sources":["../../../test/validation/projects-index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateProjectsIndex } from '../../src/validation/projects-index.js';
3
+ describe('validateProjectsIndex', () => {
4
+ it('通过合法结构(含 systems[] 与 stageCreateVersion)', () => {
5
+ const obj = {
6
+ projects: [
7
+ {
8
+ projectName: 'demo',
9
+ projectDir: '/abs/demo',
10
+ mode: 'glue',
11
+ createdAt: '2026-06-25T00:00:00Z',
12
+ stageCreateVersion: '0.0.4-alpha.0',
13
+ requirements: [],
14
+ },
15
+ ],
16
+ systems: [],
17
+ };
18
+ const result = validateProjectsIndex(obj);
19
+ expect(result.projects).toHaveLength(1);
20
+ expect(result.projects[0].stageCreateVersion).toBe('0.0.4-alpha.0');
21
+ expect(result.systems).toEqual([]);
22
+ });
23
+ it('缺 systems 字段时报结构化错误', () => {
24
+ expect(() => validateProjectsIndex({ projects: [] })).toThrow(/systems/);
25
+ });
26
+ it('项目条目缺 stageCreateVersion 报错(stage-context.md 砍掉后的元信息承接)', () => {
27
+ expect(() => validateProjectsIndex({
28
+ projects: [{ projectName: 'x', projectDir: '/x', mode: 'glue', requirements: [] }],
29
+ systems: [],
30
+ })).toThrow(/stageCreateVersion/);
31
+ });
32
+ it('不允许残留 stageContextPath / envPath / knowledgeRoot 字段', () => {
33
+ expect(() => validateProjectsIndex({
34
+ projects: [
35
+ {
36
+ projectName: 'x',
37
+ projectDir: '/x',
38
+ mode: 'glue',
39
+ createdAt: '2026-06-25T00:00:00Z',
40
+ stageCreateVersion: '0.0.4-alpha.0',
41
+ requirements: [],
42
+ stageContextPath: '/x/stage-context.md', // 应被拒绝
43
+ },
44
+ ],
45
+ systems: [],
46
+ })).toThrow(/stageContextPath|removed/);
47
+ });
48
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=testcase-v1-draft.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testcase-v1-draft.test.d.ts","sourceRoot":"","sources":["../../../test/validation/testcase-v1-draft.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,173 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateTestcase } from '../../src/validation/testcase.js';
3
+ /**
4
+ * 构造一份满足 testcase v1-draft schema 的最小合法用例对象。
5
+ *
6
+ * @returns 合法 testcase v1-draft 对象。
7
+ */
8
+ function makeValidTestcase() {
9
+ return {
10
+ schemaVersion: 'ep-stage-testcase/v1-draft',
11
+ testcaseId: 'TC-001',
12
+ contractRef: '.ep-stage/contract.json5',
13
+ scenario: '组织管理 CRUD 主流程',
14
+ moduleId: 'org-management',
15
+ moduleName: '组织管理',
16
+ runtimeContext: {
17
+ testProjectDir: '/tmp/glue-project',
18
+ caseDir: '/tmp/glue-project/src/tests/014',
19
+ specPath: '/tmp/glue-project/src/tests/014/crud.spec.ts',
20
+ },
21
+ testAccess: {
22
+ systems: [
23
+ {
24
+ systemName: '政务系统',
25
+ loginUrl: 'http://example/login',
26
+ roles: [
27
+ {
28
+ role: '业务管理员',
29
+ username: 'yz',
30
+ password: 'secret',
31
+ permissions: ['新增', '修改', '删除', '导出'],
32
+ },
33
+ ],
34
+ },
35
+ ],
36
+ },
37
+ cases: [
38
+ {
39
+ caseId: 'CASE-001',
40
+ title: '管理员新增组织',
41
+ generationStrategy: 'glue',
42
+ coverageStatus: 'covered',
43
+ reviewStatus: 'confirmed',
44
+ queryKey: '场所名称',
45
+ precondition: '使用业务管理员登录并进入列表页',
46
+ steps: '新增、查询、编辑、删除场所窗口信息',
47
+ assertion: '列表可查询到新增数据,编辑后字段更新,删除后列表不再出现',
48
+ },
49
+ ],
50
+ uncoveredCandidates: [
51
+ {
52
+ caseId: 'CASE-002',
53
+ label: '导出',
54
+ generationStrategy: 'ai',
55
+ coverageStatus: 'gap',
56
+ requiredRole: '系统管理员',
57
+ businessIntent: '导出当前查询结果',
58
+ assertionExpectation: '断言下载文件存在',
59
+ queryKey: '导出',
60
+ precondition: '使用系统管理员登录并进入列表页',
61
+ steps: '点击导出并选择当前页',
62
+ assertion: '成功导出当前列表结果',
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ describe('validateTestcase (v1-draft)', () => {
68
+ it('合法 v1-draft testcase 通过', () => {
69
+ const result = validateTestcase(makeValidTestcase());
70
+ expect(result.schemaVersion).toBe('ep-stage-testcase/v1-draft');
71
+ expect(result.cases).toHaveLength(1);
72
+ expect(result.uncoveredCandidates).toHaveLength(1);
73
+ expect(result.runtimeContext?.specPath).toBe('/tmp/glue-project/src/tests/014/crud.spec.ts');
74
+ expect(result.testAccess?.systems?.[0]?.roles[0]?.password).toBe('secret');
75
+ expect(result.cases[0]?.steps).toContain('新增');
76
+ expect(result.uncoveredCandidates[0]?.assertion).toContain('导出');
77
+ });
78
+ it('testAccess 允许角色账号密码缺省,供 generate 交互补齐', () => {
79
+ const tc = makeValidTestcase();
80
+ const systems = tc.testAccess.systems;
81
+ delete systems[0]?.roles[0]?.username;
82
+ delete systems[0]?.roles[0]?.password;
83
+ const result = validateTestcase(tc);
84
+ expect(result.testAccess?.systems?.[0]?.roles[0]?.username).toBeUndefined();
85
+ expect(result.testAccess?.systems?.[0]?.roles[0]?.password).toBeUndefined();
86
+ });
87
+ it('testAccess 允许仅引用 credentials.json5 并声明 requiredRoles', () => {
88
+ const tc = makeValidTestcase();
89
+ tc.testAccess = {
90
+ credentialRef: 'credentials.json5',
91
+ requiredRoles: ['业务管理员'],
92
+ };
93
+ const result = validateTestcase(tc);
94
+ expect(result.testAccess?.credentialRef).toBe('credentials.json5');
95
+ expect(result.testAccess?.requiredRoles).toEqual(['业务管理员']);
96
+ });
97
+ it('runtimeContext 结构非法时报错', () => {
98
+ const tc = makeValidTestcase();
99
+ delete tc.runtimeContext.specPath;
100
+ expect(() => validateTestcase(tc)).toThrow(/runtimeContext\.specPath/);
101
+ });
102
+ it('schemaVersion 非 ep-stage-testcase/v1-draft 报错', () => {
103
+ const tc = { ...makeValidTestcase(), schemaVersion: 'v2' };
104
+ expect(() => validateTestcase(tc)).toThrow(/schemaVersion/);
105
+ });
106
+ it('menuPath 可缺省', () => {
107
+ const result = validateTestcase(makeValidTestcase());
108
+ expect(result.menuPath).toBeUndefined();
109
+ });
110
+ it('缺 moduleId 报错', () => {
111
+ const tc = makeValidTestcase();
112
+ delete tc.moduleId;
113
+ expect(() => validateTestcase(tc)).toThrow(/moduleId/);
114
+ });
115
+ it('缺 moduleName 报错', () => {
116
+ const tc = makeValidTestcase();
117
+ delete tc.moduleName;
118
+ expect(() => validateTestcase(tc)).toThrow(/moduleName/);
119
+ });
120
+ it('缺 contractRef 报错', () => {
121
+ const tc = makeValidTestcase();
122
+ delete tc.contractRef;
123
+ expect(() => validateTestcase(tc)).toThrow(/contractRef/);
124
+ });
125
+ it('cases[].generationStrategy 非 glue 报错', () => {
126
+ const tc = makeValidTestcase();
127
+ const cases = tc.cases;
128
+ cases[0].generationStrategy = 'ai';
129
+ expect(() => validateTestcase(tc)).toThrow(/generationStrategy/);
130
+ });
131
+ it('cases[].reviewStatus 缺失报错', () => {
132
+ const tc = makeValidTestcase();
133
+ const cases = tc.cases;
134
+ delete cases[0].reviewStatus;
135
+ expect(() => validateTestcase(tc)).toThrow(/reviewStatus/);
136
+ });
137
+ it('cases[].reviewStatus 非法值报错', () => {
138
+ const tc = makeValidTestcase();
139
+ const cases = tc.cases;
140
+ cases[0].reviewStatus = 'unknown';
141
+ expect(() => validateTestcase(tc)).toThrow(/reviewStatus/);
142
+ });
143
+ it('uncoveredCandidates[].generationStrategy=glue 报错', () => {
144
+ const tc = makeValidTestcase();
145
+ const unc = tc.uncoveredCandidates;
146
+ unc[0].generationStrategy = 'glue';
147
+ expect(() => validateTestcase(tc)).toThrow(/generationStrategy/);
148
+ });
149
+ it('uncoveredCandidates[].generationStrategy=manual 报错', () => {
150
+ const tc = makeValidTestcase();
151
+ const unc = tc.uncoveredCandidates;
152
+ unc[0].generationStrategy = 'manual';
153
+ expect(() => validateTestcase(tc)).toThrow(/generationStrategy/);
154
+ });
155
+ it('uncoveredCandidates[].requiredRole 缺失报错', () => {
156
+ const tc = makeValidTestcase();
157
+ const unc = tc.uncoveredCandidates;
158
+ delete unc[0].requiredRole;
159
+ expect(() => validateTestcase(tc)).toThrow(/requiredRole/);
160
+ });
161
+ it('uncoveredCandidates[].businessIntent 缺失报错', () => {
162
+ const tc = makeValidTestcase();
163
+ const unc = tc.uncoveredCandidates;
164
+ delete unc[0].businessIntent;
165
+ expect(() => validateTestcase(tc)).toThrow(/businessIntent/);
166
+ });
167
+ it('uncoveredCandidates[].assertionExpectation 缺失报错', () => {
168
+ const tc = makeValidTestcase();
169
+ const unc = tc.uncoveredCandidates;
170
+ delete unc[0].assertionExpectation;
171
+ expect(() => validateTestcase(tc)).toThrow(/assertionExpectation/);
172
+ });
173
+ });
@@ -0,0 +1,3 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
3
+ //# sourceMappingURL=vitest.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAEA,wBAKG"}