@epoint-testtech/ep-stage-skill 0.0.4-alpha.0 → 0.0.5

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 (206) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -186
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -199
  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 -112
  6. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -0
  7. package/codex-skill/ep-stage/glue-run-test/SKILL.md +292 -249
  8. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -0
  9. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -145
  10. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -43
  11. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  12. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +166 -0
  13. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +233 -0
  14. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  15. package/codex-skill/ep-stage/page-element-scan/SKILL.md +91 -0
  16. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +37 -0
  17. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +85 -0
  18. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +153 -0
  19. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +233 -0
  20. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +298 -0
  21. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +749 -0
  22. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  23. package/codex-skill/ep-stage/scripts/validate-skill.mjs +98 -95
  24. package/dist/src/cli/generate-testcase.d.ts +18 -51
  25. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  26. package/dist/src/cli/generate-testcase.js +127 -131
  27. package/dist/src/cli/index.d.ts +11 -2
  28. package/dist/src/cli/index.d.ts.map +1 -1
  29. package/dist/src/cli/index.js +49 -16
  30. package/dist/src/cli/probe.d.ts +2 -0
  31. package/dist/src/cli/probe.d.ts.map +1 -1
  32. package/dist/src/cli/probe.js +239 -13
  33. package/dist/src/cli/run.d.ts +136 -29
  34. package/dist/src/cli/run.d.ts.map +1 -1
  35. package/dist/src/cli/run.js +494 -65
  36. package/dist/src/cli/spec.d.ts +18 -11
  37. package/dist/src/cli/spec.d.ts.map +1 -1
  38. package/dist/src/cli/spec.js +74 -20
  39. package/dist/src/contracts/crud-business-module.d.ts +6 -0
  40. package/dist/src/contracts/crud-business-module.d.ts.map +1 -1
  41. package/dist/src/contracts/glue-contract-v1.d.ts +98 -0
  42. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -0
  43. package/dist/src/contracts/glue-contract-v1.js +9 -0
  44. package/dist/src/contracts/inference-trace.d.ts +223 -0
  45. package/dist/src/contracts/inference-trace.d.ts.map +1 -0
  46. package/dist/src/contracts/inference-trace.js +12 -0
  47. package/dist/src/contracts/observable-chain.d.ts +4 -0
  48. package/dist/src/contracts/observable-chain.d.ts.map +1 -1
  49. package/dist/src/extractors/code-list-json5.d.ts +192 -0
  50. package/dist/src/extractors/code-list-json5.d.ts.map +1 -0
  51. package/dist/src/extractors/code-list-json5.js +91 -0
  52. package/dist/src/extractors/code-list.d.ts +3 -3
  53. package/dist/src/extractors/code-list.d.ts.map +1 -1
  54. package/dist/src/extractors/code-list.js +46 -11
  55. package/dist/src/extractors/html-page.d.ts +9 -0
  56. package/dist/src/extractors/html-page.d.ts.map +1 -1
  57. package/dist/src/extractors/html-page.js +26 -2
  58. package/dist/src/extractors/prd-extractor.d.ts +70 -0
  59. package/dist/src/extractors/prd-extractor.d.ts.map +1 -0
  60. package/dist/src/extractors/prd-extractor.js +248 -0
  61. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  62. package/dist/src/generators/stage-skeleton-script.d.ts +87 -20
  63. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  64. package/dist/src/generators/stage-skeleton-script.js +656 -238
  65. package/dist/src/index.d.ts +7 -2
  66. package/dist/src/index.d.ts.map +1 -1
  67. package/dist/src/index.js +3 -1
  68. package/dist/src/inference/module-hints-inference.d.ts +30 -0
  69. package/dist/src/inference/module-hints-inference.d.ts.map +1 -0
  70. package/dist/src/inference/module-hints-inference.js +363 -0
  71. package/dist/src/inference/source-analysis.d.ts +26 -0
  72. package/dist/src/inference/source-analysis.d.ts.map +1 -0
  73. package/dist/src/inference/source-analysis.js +258 -0
  74. package/dist/src/normalizers/crud-contract.d.ts +3 -0
  75. package/dist/src/normalizers/crud-contract.d.ts.map +1 -1
  76. package/dist/src/normalizers/crud-contract.js +3 -0
  77. package/dist/src/runtime/ai-runtime-trace.d.ts +88 -0
  78. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -0
  79. package/dist/src/runtime/ai-runtime-trace.js +119 -0
  80. package/dist/src/testcase/button-interaction-inference.d.ts +37 -0
  81. package/dist/src/testcase/button-interaction-inference.d.ts.map +1 -0
  82. package/dist/src/testcase/button-interaction-inference.js +385 -0
  83. package/dist/src/testcase/contract-testcase-assembler.d.ts +44 -0
  84. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -0
  85. package/dist/src/testcase/contract-testcase-assembler.js +806 -0
  86. package/dist/src/testcase/prd-button-mapper.d.ts +72 -0
  87. package/dist/src/testcase/prd-button-mapper.d.ts.map +1 -0
  88. package/dist/src/testcase/prd-button-mapper.js +148 -0
  89. package/dist/src/testcase/runtime-context.d.ts +9 -0
  90. package/dist/src/testcase/runtime-context.d.ts.map +1 -0
  91. package/dist/src/testcase/runtime-context.js +1 -0
  92. package/dist/src/testcase/test-access.d.ts +38 -0
  93. package/dist/src/testcase/test-access.d.ts.map +1 -0
  94. package/dist/src/testcase/test-access.js +106 -0
  95. package/dist/src/testcase/testcase-execution-fields.d.ts +10 -0
  96. package/dist/src/testcase/testcase-execution-fields.d.ts.map +1 -0
  97. package/dist/src/testcase/testcase-execution-fields.js +1 -0
  98. package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
  99. package/dist/src/testcase/testcase-generator.js +6 -0
  100. package/dist/src/testcase/testcase-markdown.d.ts +11 -0
  101. package/dist/src/testcase/testcase-markdown.d.ts.map +1 -0
  102. package/dist/src/testcase/testcase-markdown.js +181 -0
  103. package/dist/src/testcase/testcase-skeleton.d.ts +4 -0
  104. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -1
  105. package/dist/src/testcase/testcase-spec-assembly.d.ts +4 -0
  106. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -1
  107. package/dist/src/testcase/testcase-v1-draft.d.ts +58 -0
  108. package/dist/src/testcase/testcase-v1-draft.d.ts.map +1 -0
  109. package/dist/src/testcase/testcase-v1-draft.js +13 -0
  110. package/dist/src/validation/contract.d.ts +14 -0
  111. package/dist/src/validation/contract.d.ts.map +1 -0
  112. package/dist/src/validation/contract.js +132 -0
  113. package/dist/src/validation/credentials.d.ts +7 -2
  114. package/dist/src/validation/credentials.d.ts.map +1 -1
  115. package/dist/src/validation/credentials.js +15 -4
  116. package/dist/src/validation/index.d.ts +2 -0
  117. package/dist/src/validation/index.d.ts.map +1 -1
  118. package/dist/src/validation/index.js +2 -0
  119. package/dist/src/validation/inference-trace.d.ts +13 -0
  120. package/dist/src/validation/inference-trace.d.ts.map +1 -0
  121. package/dist/src/validation/inference-trace.js +31 -0
  122. package/dist/src/validation/testcase.d.ts +9 -7
  123. package/dist/src/validation/testcase.d.ts.map +1 -1
  124. package/dist/src/validation/testcase.js +181 -34
  125. package/docs/README.md +124 -134
  126. package/docs/mvp-usage-guide.md +298 -298
  127. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  128. package/examples/schemeresource.module-hints.json +38 -38
  129. package/examples/schemeresource.observable.code_list.md +37 -37
  130. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  131. package/examples/zwplace-placecategory-validation.json +29 -29
  132. package/examples/zwplace.module-hints.json +69 -69
  133. package/examples/zwplace.observable.code_list.md +37 -37
  134. package/package.json +2 -2
  135. package/dist/test/cli/extract-contract.test.d.ts +0 -2
  136. package/dist/test/cli/extract-contract.test.d.ts.map +0 -1
  137. package/dist/test/cli/extract-contract.test.js +0 -32
  138. package/dist/test/cli/generate-testcase.test.d.ts +0 -2
  139. package/dist/test/cli/generate-testcase.test.d.ts.map +0 -1
  140. package/dist/test/cli/generate-testcase.test.js +0 -130
  141. package/dist/test/cli/index.test.d.ts +0 -2
  142. package/dist/test/cli/index.test.d.ts.map +0 -1
  143. package/dist/test/cli/index.test.js +0 -93
  144. package/dist/test/cli/run.test.d.ts +0 -2
  145. package/dist/test/cli/run.test.d.ts.map +0 -1
  146. package/dist/test/cli/run.test.js +0 -149
  147. package/dist/test/cli/spec.test.d.ts +0 -2
  148. package/dist/test/cli/spec.test.d.ts.map +0 -1
  149. package/dist/test/cli/spec.test.js +0 -196
  150. package/dist/test/crud-contract.test.d.ts +0 -2
  151. package/dist/test/crud-contract.test.d.ts.map +0 -1
  152. package/dist/test/crud-contract.test.js +0 -819
  153. package/dist/test/gap-inference.test.d.ts +0 -2
  154. package/dist/test/gap-inference.test.d.ts.map +0 -1
  155. package/dist/test/gap-inference.test.js +0 -597
  156. package/dist/test/generalization.test.d.ts +0 -2
  157. package/dist/test/generalization.test.d.ts.map +0 -1
  158. package/dist/test/generalization.test.js +0 -73
  159. package/dist/test/material-inventory.test.d.ts +0 -2
  160. package/dist/test/material-inventory.test.d.ts.map +0 -1
  161. package/dist/test/material-inventory.test.js +0 -141
  162. package/dist/test/observable-chain.test.d.ts +0 -2
  163. package/dist/test/observable-chain.test.d.ts.map +0 -1
  164. package/dist/test/observable-chain.test.js +0 -123
  165. package/dist/test/observable-pipeline.test.d.ts +0 -2
  166. package/dist/test/observable-pipeline.test.d.ts.map +0 -1
  167. package/dist/test/observable-pipeline.test.js +0 -461
  168. package/dist/test/page-structure.test.d.ts +0 -2
  169. package/dist/test/page-structure.test.d.ts.map +0 -1
  170. package/dist/test/page-structure.test.js +0 -45
  171. package/dist/test/scenario-inference.test.d.ts +0 -2
  172. package/dist/test/scenario-inference.test.d.ts.map +0 -1
  173. package/dist/test/scenario-inference.test.js +0 -73
  174. package/dist/test/stage-context.test.d.ts +0 -2
  175. package/dist/test/stage-context.test.d.ts.map +0 -1
  176. package/dist/test/stage-context.test.js +0 -395
  177. package/dist/test/testcase-generator.test.d.ts +0 -2
  178. package/dist/test/testcase-generator.test.d.ts.map +0 -1
  179. package/dist/test/testcase-generator.test.js +0 -276
  180. package/dist/test/testcase-skeleton.test.d.ts +0 -2
  181. package/dist/test/testcase-skeleton.test.d.ts.map +0 -1
  182. package/dist/test/testcase-skeleton.test.js +0 -185
  183. package/dist/test/testcase-spec-assembly.test.d.ts +0 -2
  184. package/dist/test/testcase-spec-assembly.test.d.ts.map +0 -1
  185. package/dist/test/testcase-spec-assembly.test.js +0 -105
  186. package/dist/test/util/credentials.test.d.ts +0 -2
  187. package/dist/test/util/credentials.test.d.ts.map +0 -1
  188. package/dist/test/util/credentials.test.js +0 -64
  189. package/dist/test/util/i18n-testcase.test.d.ts +0 -2
  190. package/dist/test/util/i18n-testcase.test.d.ts.map +0 -1
  191. package/dist/test/util/i18n-testcase.test.js +0 -119
  192. package/dist/test/util/softlink.test.d.ts +0 -2
  193. package/dist/test/util/softlink.test.d.ts.map +0 -1
  194. package/dist/test/util/softlink.test.js +0 -82
  195. package/dist/test/validation/credentials.test.d.ts +0 -2
  196. package/dist/test/validation/credentials.test.d.ts.map +0 -1
  197. package/dist/test/validation/credentials.test.js +0 -72
  198. package/dist/test/validation/projects-index.test.d.ts +0 -2
  199. package/dist/test/validation/projects-index.test.d.ts.map +0 -1
  200. package/dist/test/validation/projects-index.test.js +0 -48
  201. package/dist/test/validation/testcase.test.d.ts +0 -2
  202. package/dist/test/validation/testcase.test.d.ts.map +0 -1
  203. package/dist/test/validation/testcase.test.js +0 -129
  204. package/dist/vitest.config.d.ts +0 -3
  205. package/dist/vitest.config.d.ts.map +0 -1
  206. package/dist/vitest.config.js +0 -7
@@ -1,185 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { mapContractToCrudTestcaseSlots, renderGlueTestcaseSkeleton, selectTestcaseSkeletonByScenario, } from '../src/index.js';
3
- /**
4
- * 构造用例骨架映射测试所需的最小 CRUD 契约。
5
- *
6
- * @returns 包含完整 CRUD 槽位来源的测试契约。
7
- */
8
- function createCrudContract() {
9
- return {
10
- contractVersion: 'crud-business-module/v1',
11
- module: {
12
- id: 'zwplace',
13
- label: '场所窗口信息管理',
14
- },
15
- pages: {
16
- list: {
17
- role: 'list',
18
- pageId: 'gxhzwplacelist',
19
- title: '场所窗口信息列表',
20
- iframeSrcKeyword: 'gxhzwplacelist',
21
- fields: [],
22
- buttons: [],
23
- grids: [],
24
- dialogs: [],
25
- sources: [],
26
- },
27
- },
28
- dataKey: {
29
- field: 'windowname',
30
- label: '窗口名称',
31
- availability: 'derivable',
32
- generationStrategy: 'timestamp_prefix',
33
- sources: [],
34
- },
35
- searchConditions: [
36
- {
37
- field: 'windowname',
38
- label: '窗口名称',
39
- component: 'input',
40
- seedValue: '自动化测试窗口',
41
- generationStrategy: 'timestamp_prefix',
42
- sources: [],
43
- },
44
- ],
45
- flows: {
46
- create: {
47
- entryButton: {
48
- pageRole: 'list',
49
- label: '新增场所窗口',
50
- sources: [],
51
- },
52
- targetPageId: 'gxhzwplaceadd',
53
- saveButton: {
54
- pageRole: 'add',
55
- label: '保存并关闭',
56
- sources: [],
57
- },
58
- overrideFields: [],
59
- },
60
- search: {
61
- searchField: {
62
- pageRole: 'list',
63
- field: 'windowname',
64
- label: '窗口名称',
65
- sources: [],
66
- },
67
- submitControl: {
68
- pageRole: 'list',
69
- label: '查询',
70
- sources: [],
71
- },
72
- resultGrid: 'datagrid',
73
- },
74
- update: {
75
- entryAction: {
76
- pageRole: 'list',
77
- label: '修改',
78
- sources: [],
79
- },
80
- targetPageId: 'gxhzwplaceedit',
81
- updateField: {
82
- pageRole: 'edit',
83
- field: 'windowname',
84
- label: '窗口名称',
85
- sources: [],
86
- },
87
- saveButton: {
88
- pageRole: 'edit',
89
- label: '保存并关闭',
90
- sources: [],
91
- },
92
- },
93
- delete: {
94
- entryButton: {
95
- pageRole: 'list',
96
- label: '删除选定',
97
- sources: [],
98
- },
99
- expectedOutcome: 'success_delete',
100
- confirmControl: {
101
- pageRole: 'list',
102
- label: '确定',
103
- sources: [],
104
- },
105
- },
106
- },
107
- assertions: [],
108
- businessRules: [],
109
- unresolvedSlots: [],
110
- };
111
- }
112
- describe('glue testcase skeleton', () => {
113
- it('selects the CRUD testcase skeleton for a single-page CRUD scenario', () => {
114
- expect(selectTestcaseSkeletonByScenario('crud.single-page')).toMatchObject({
115
- skeletonId: 'crud.skeleton-testcase/v1',
116
- });
117
- });
118
- it('selects the same CRUD testcase skeleton for a nested CRUD pilot scenario', () => {
119
- expect(selectTestcaseSkeletonByScenario('crud.nested')).toMatchObject({
120
- skeletonId: 'crud.skeleton-testcase/v1',
121
- });
122
- });
123
- it('maps a CRUD contract to testcase slots without guessing unresolved business values', () => {
124
- const slots = mapContractToCrudTestcaseSlots({
125
- contract: createCrudContract(),
126
- scenario: 'crud.single-page',
127
- menu: '场所窗口信息管理>场所窗口信息列表',
128
- });
129
- expect(slots).toMatchObject({
130
- moduleId: 'zwplace',
131
- moduleName: '场所窗口信息管理',
132
- listPageTitle: '场所窗口信息列表',
133
- createActionLabel: '新增场所窗口',
134
- searchFieldLabel: '窗口名称',
135
- envBaseUrl: '.env: LOGIN_SYSTEM_URL',
136
- envUsername: '.env: LOGIN_USERNAME',
137
- envPassword: '.env: LOGIN_PASSWORD',
138
- unresolved: [],
139
- });
140
- });
141
- it('treats unresolved menu placeholders as unresolved instead of resolved slot sources', () => {
142
- const slots = mapContractToCrudTestcaseSlots({
143
- contract: createCrudContract(),
144
- scenario: 'crud.single-page',
145
- menu: '未确认菜单路径',
146
- });
147
- expect(slots.reviewStatus).toBe('needs_review');
148
- expect(slots.menu).toBe('待人工确认');
149
- expect(slots.unresolved).toContain('menu: 缺少 input.menu');
150
- expect(slots.slotSources).not.toContain('menu=input.menu');
151
- });
152
- it('records missing contract slots as unresolved without guessing business values', () => {
153
- const contract = createCrudContract();
154
- delete contract.flows.create;
155
- const slots = mapContractToCrudTestcaseSlots({
156
- contract,
157
- scenario: 'crud.single-page',
158
- menu: '场所窗口信息管理>场所窗口信息列表',
159
- });
160
- expect(slots.reviewStatus).toBe('needs_review');
161
- expect(slots.createActionLabel).toBe('待人工确认');
162
- expect(slots.unresolved).toContain('createActionLabel: 缺少 contract.flows.create.entryButton.label');
163
- });
164
- it('renders the testcase skeleton template from slots', () => {
165
- const template = [
166
- '# {{moduleName}}',
167
- '',
168
- '## {{createCaseTitle}}',
169
- '',
170
- '- 列表页:{{listPageTitle}}',
171
- '- 步骤:{{createSteps}}',
172
- '- 查询字段:{{searchFieldLabel}}',
173
- ].join('\n');
174
- const markdown = renderGlueTestcaseSkeleton(template, mapContractToCrudTestcaseSlots({
175
- contract: createCrudContract(),
176
- scenario: 'crud.single-page',
177
- menu: '场所窗口信息管理>场所窗口信息列表',
178
- }));
179
- expect(markdown).toContain('# 场所窗口信息管理');
180
- expect(markdown).toContain('## 新增场所窗口信息管理');
181
- expect(markdown).toContain('列表页:场所窗口信息列表');
182
- expect(markdown).toContain('查询字段:窗口名称');
183
- expect(markdown).not.toContain('{{');
184
- });
185
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=testcase-spec-assembly.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testcase-spec-assembly.test.d.ts","sourceRoot":"","sources":["../../test/testcase-spec-assembly.test.ts"],"names":[],"mappings":""}
@@ -1,105 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { validateGlueTestcaseDocumentForSpecAssembly } from '../src/testcase/testcase-spec-assembly.js';
3
- /**
4
- * 构造 spec assembly 校验所需的最小契约。
5
- *
6
- * @returns CRUD 业务模块契约的模块标识部分。
7
- */
8
- function createContract() {
9
- return {
10
- contractVersion: 'crud-business-module/v1',
11
- module: {
12
- id: 'zwplace',
13
- label: '场所窗口信息管理',
14
- },
15
- pages: {
16
- list: {
17
- role: 'list',
18
- pageId: 'gxhzwplacelist',
19
- title: '场所窗口信息列表',
20
- iframeSrcKeyword: 'gxhzwplacelist',
21
- fields: [],
22
- buttons: [],
23
- grids: [],
24
- dialogs: [],
25
- sources: [],
26
- },
27
- },
28
- dataKey: {
29
- field: 'windowname',
30
- label: '窗口名称',
31
- availability: 'derivable',
32
- generationStrategy: 'timestamp_prefix',
33
- sources: [],
34
- },
35
- flows: {},
36
- assertions: [],
37
- businessRules: [],
38
- unresolvedSlots: [],
39
- };
40
- }
41
- /**
42
- * 构造已确认的 CRUD 四工作流用例文档。
43
- *
44
- * @returns 胶水测试用例文档。
45
- */
46
- function createConfirmedTestcaseDocument() {
47
- return {
48
- moduleId: 'zwplace',
49
- moduleName: '场所窗口信息管理',
50
- cases: [
51
- createCase('zwplace.create', 'Create:新增场所窗口信息管理'),
52
- createCase('zwplace.read', 'Read:搜索场所窗口信息管理'),
53
- createCase('zwplace.update', 'Update:修改场所窗口信息管理'),
54
- createCase('zwplace.delete', 'Delete:删除场所窗口信息管理'),
55
- ],
56
- reasoningSummary: {
57
- conclusion: '已确认 CRUD 用例。',
58
- evidenceChain: [],
59
- alternatives: [],
60
- confidence: 'high',
61
- risks: [],
62
- needsHumanReview: false,
63
- },
64
- };
65
- }
66
- /**
67
- * 构造单条已确认用例。
68
- *
69
- * @param caseId 用例 ID。
70
- * @param title 用例标题。
71
- * @returns 胶水测试用例。
72
- */
73
- function createCase(caseId, title) {
74
- return {
75
- caseId,
76
- title,
77
- scenario: 'crud.single-page',
78
- reviewStatus: 'confirmed',
79
- evidence: [],
80
- steps: ['执行动作'],
81
- assertions: ['验证结果'],
82
- unresolved: [],
83
- };
84
- }
85
- describe('testcase spec assembly contract', () => {
86
- it('accepts confirmed testcase documents that match the contract CRUD workflow granularity', () => {
87
- expect(() => validateGlueTestcaseDocumentForSpecAssembly(createConfirmedTestcaseDocument(), createContract())).not.toThrow();
88
- });
89
- it('rejects testcase documents that still need human review', () => {
90
- const document = createConfirmedTestcaseDocument();
91
- document.cases[1].reviewStatus = 'needs_review';
92
- document.cases[1].unresolved = ['menu: 缺少 input.menu'];
93
- expect(() => validateGlueTestcaseDocumentForSpecAssembly(document, createContract())).toThrow('glue testcase 仍有未确认用例');
94
- });
95
- it('rejects testcase documents that do not match the contract module', () => {
96
- const document = createConfirmedTestcaseDocument();
97
- document.moduleId = 'other-module';
98
- expect(() => validateGlueTestcaseDocumentForSpecAssembly(document, createContract())).toThrow('glue testcase moduleId 与 contract 不一致');
99
- });
100
- it('rejects testcase documents missing CRUD workflow cases required by the generated spec', () => {
101
- const document = createConfirmedTestcaseDocument();
102
- document.cases = document.cases.filter((item) => item.caseId !== 'zwplace.update');
103
- expect(() => validateGlueTestcaseDocumentForSpecAssembly(document, createContract())).toThrow('glue testcase 缺少 spec assembly 需要的用例');
104
- });
105
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=credentials.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credentials.test.d.ts","sourceRoot":"","sources":["../../../test/util/credentials.test.ts"],"names":[],"mappings":""}
@@ -1,64 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { loadCredentials } from '../../src/util/credentials.js';
6
- /**
7
- * REQ-ENV-02 / REQ-ENV-03:从 <需求名>/credentials.json5 加载数组形态凭据。
8
- *
9
- * 不重复实现 schema 校验(已由 validateCredentials 覆盖),本测试关注 IO 行为:
10
- * 文件读取、JSON5 解析、对校验器的组合调用,以及缺失场景的友好错误。
11
- */
12
- describe('loadCredentials', () => {
13
- let tmpDir;
14
- let credPath;
15
- beforeEach(() => {
16
- tmpDir = mkdtempSync(path.join(os.tmpdir(), 'credentials-test-'));
17
- credPath = path.join(tmpDir, 'credentials.json5');
18
- });
19
- afterEach(() => {
20
- rmSync(tmpDir, { recursive: true, force: true });
21
- });
22
- it('读合法数组形态凭据返回 Credential[]', () => {
23
- writeFileSync(credPath, `[
24
- // 普通账号
25
- { url: 'https://demo.example.com', username: 'alice', password: 'p1', role: 'admin', systemName: '后台' },
26
- { url: 'https://demo.example.com', username: 'bob', password: 'p2', role: 'user' },
27
- ]`);
28
- const list = loadCredentials(credPath);
29
- expect(list).toHaveLength(2);
30
- expect(list[0]).toMatchObject({
31
- url: 'https://demo.example.com',
32
- username: 'alice',
33
- password: 'p1',
34
- role: 'admin',
35
- systemName: '后台',
36
- });
37
- expect(list[1]).toMatchObject({ username: 'bob', role: 'user' });
38
- expect(list[1].systemName).toBeUndefined();
39
- });
40
- it('单条凭据无可选字段也能解析', () => {
41
- writeFileSync(credPath, `[{ url: 'https://x.test', username: 'u', password: 'p' }]`);
42
- const list = loadCredentials(credPath);
43
- expect(list).toHaveLength(1);
44
- expect(list[0].role).toBeUndefined();
45
- expect(list[0].systemName).toBeUndefined();
46
- });
47
- it('缺必填字段 url 时抛 schema 错误(来自 validateCredentials)', () => {
48
- writeFileSync(credPath, `[{ username: 'u', password: 'p' }]`);
49
- expect(() => loadCredentials(credPath)).toThrow(/缺必填字段 url/);
50
- });
51
- it('顶层不是数组时抛 schema 错误', () => {
52
- writeFileSync(credPath, `{ url: 'x', username: 'u', password: 'p' }`);
53
- expect(() => loadCredentials(credPath)).toThrow(/顶层必须是数组/);
54
- });
55
- it('文件不存在时抛带路径的友好错误', () => {
56
- const missing = path.join(tmpDir, 'not-here.json5');
57
- expect(() => loadCredentials(missing)).toThrow(/credentials\.json5 不存在/);
58
- expect(() => loadCredentials(missing)).toThrow(new RegExp(missing.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
59
- });
60
- it('JSON5 语法错误时抛解析错误', () => {
61
- writeFileSync(credPath, `[ { url: 'x', username: 'u', password: 'p' `); // 缺右括号
62
- expect(() => loadCredentials(credPath)).toThrow();
63
- });
64
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=i18n-testcase.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"i18n-testcase.test.d.ts","sourceRoot":"","sources":["../../../test/util/i18n-testcase.test.ts"],"names":[],"mappings":""}
@@ -1,119 +0,0 @@
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
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=softlink.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"softlink.test.d.ts","sourceRoot":"","sources":["../../../test/util/softlink.test.ts"],"names":[],"mappings":""}
@@ -1,82 +0,0 @@
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
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=credentials.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credentials.test.d.ts","sourceRoot":"","sources":["../../../test/validation/credentials.test.ts"],"names":[],"mappings":""}
@@ -1,72 +0,0 @@
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
- username: 'admin',
18
- password: 'pwd',
19
- role: '系统管理员',
20
- systemName: '基础平台',
21
- });
22
- });
23
- it('允许无 role / systemName 的条目', () => {
24
- const result = validateCredentials([
25
- { url: 'http://example.test', username: 'admin', password: 'pwd' },
26
- ]);
27
- expect(result[0]).toEqual({
28
- url: 'http://example.test',
29
- username: 'admin',
30
- password: 'pwd',
31
- });
32
- expect(result[0]).not.toHaveProperty('role');
33
- expect(result[0]).not.toHaveProperty('systemName');
34
- });
35
- it('非数组报错', () => {
36
- expect(() => validateCredentials({ url: 'x' })).toThrow(/数组/);
37
- });
38
- it('空数组报错', () => {
39
- expect(() => validateCredentials([])).toThrow(/至少|1 条/);
40
- });
41
- it('条目缺 url 报错', () => {
42
- expect(() => validateCredentials([{ username: 'admin', password: 'pwd' }])).toThrow(/url/);
43
- });
44
- it('条目缺 username 报错', () => {
45
- expect(() => validateCredentials([{ url: 'http://example.test', password: 'pwd' }])).toThrow(/username/);
46
- });
47
- it('条目缺 password 报错', () => {
48
- expect(() => validateCredentials([{ url: 'http://example.test', username: 'admin' }])).toThrow(/password/);
49
- });
50
- it('条目空字符串字段(空白)报错', () => {
51
- expect(() => validateCredentials([{ url: ' ', username: 'admin', password: 'pwd' }])).toThrow(/url/);
52
- });
53
- it('同 (url, role) 重复报错', () => {
54
- expect(() => validateCredentials([
55
- { url: 'http://example.test', username: 'a', password: 'p1', role: '管理员' },
56
- { url: 'http://example.test', username: 'b', password: 'p2', role: '管理员' },
57
- ])).toThrow(/重复/);
58
- });
59
- it('同 url 但 role 不同允许', () => {
60
- const result = validateCredentials([
61
- { url: 'http://example.test', username: 'a', password: 'p1', role: '管理员' },
62
- { url: 'http://example.test', username: 'b', password: 'p2', role: '业务员' },
63
- ]);
64
- expect(result).toHaveLength(2);
65
- });
66
- it('同 url 都没 role 视作 (url, "") 也会触发重复', () => {
67
- expect(() => validateCredentials([
68
- { url: 'http://example.test', username: 'a', password: 'p1' },
69
- { url: 'http://example.test', username: 'b', password: 'p2' },
70
- ])).toThrow(/重复/);
71
- });
72
- });