@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,154 @@
1
+ /**
2
+ * generate-testcase JSON5 新产线单元测试(Task 3)。
3
+ *
4
+ * 验收范围(与 brief 一致):
5
+ * - 产出 contract.json5 / testcase.json5 / inference-trace.json(不产出 contract.json)
6
+ * - v1-draft schema 校验通过
7
+ * - skeletonSlots.crud 存在且形态对齐 StageSkeletonCrudFlowSlots
8
+ * - cases 全部 generationStrategy='glue'
9
+ * - uncoveredCandidates 至少存在 generationStrategy='ai'
10
+ * - testcase.menuPath 不再是必填字段,不从 roles[0].menuPath 推导
11
+ *
12
+ * 注意:本测试依赖 e2e 014 _docs/code_list.json5(新协议样例)。
13
+ */
14
+ import { describe, expect, it, vi } from 'vitest';
15
+ import fs from 'node:fs';
16
+ import os from 'node:os';
17
+ import path from 'node:path';
18
+ import JSON5 from 'json5';
19
+ import { runGenerateTestcase } from '../../src/cli/generate-testcase.js';
20
+ import { writeProjectIndex } from '../../src/context/stage-context.js';
21
+ const REPO_CODE_LIST = path.resolve(__dirname, '../../../../e2e/glue-code-mvp/src/tests/014-测试需求10-20260622/_docs/code_list.json5');
22
+ describe('generate testcase from code_list.json5', () => {
23
+ it('writes contract.json5 + testcase.json5 + inference-trace.json (no legacy outputs)', () => {
24
+ const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ep-stage-home-'));
25
+ const testsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ep-stage-json5-'));
26
+ const projectDir = path.join(testsDir, 'glue-project');
27
+ const homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
28
+ try {
29
+ writeProjectIndex({
30
+ homeDir,
31
+ projects: [
32
+ {
33
+ projectName: 'glue-project',
34
+ projectDir,
35
+ mode: 'glue',
36
+ stageCreateVersion: 'test',
37
+ requirements: [],
38
+ },
39
+ ],
40
+ systems: [],
41
+ });
42
+ const result = runGenerateTestcase({
43
+ codeList: REPO_CODE_LIST,
44
+ testsDir,
45
+ projectDir,
46
+ directUrl: 'http://example.test/epoint-web/perpage/zwplace/gxhzwplacelist.html',
47
+ });
48
+ expect(result.testcasePath).toBe(path.join(testsDir, 'testcase.json5'));
49
+ expect(fs.existsSync(path.join(testsDir, '.ep-stage', 'contract.json5'))).toBe(true);
50
+ expect(fs.existsSync(path.join(testsDir, '.ep-stage', 'inference-trace.json'))).toBe(true);
51
+ expect(fs.existsSync(path.join(testsDir, '.ep-stage', 'contract.json'))).toBe(false); // 旧产物不再生成
52
+ const contract = JSON5.parse(fs.readFileSync(path.join(testsDir, '.ep-stage', 'contract.json5'), 'utf8'));
53
+ const testcase = JSON5.parse(fs.readFileSync(path.join(testsDir, 'testcase.json5'), 'utf8'));
54
+ expect(contract.schemaVersion).toBe('ep-stage-contract/v1-draft');
55
+ expect(testcase.schemaVersion).toBe('ep-stage-testcase/v1-draft');
56
+ expect(contract.base.navigation.directUrl).toBe('http://example.test/epoint-web/perpage/zwplace/gxhzwplacelist.html');
57
+ expect(contract.scenario.searchFields.map((i) => i.field)).toEqual([
58
+ 'placename',
59
+ 'placecategory',
60
+ 'status',
61
+ ]);
62
+ expect(contract.scenario.skeletonSlots.crud).toBeTruthy();
63
+ expect(testcase.cases.every((c) => c.generationStrategy === 'glue')).toBe(true);
64
+ expect(testcase.uncoveredCandidates.some((c) => c.generationStrategy === 'ai')).toBe(true);
65
+ expect(testcase.menuPath).toBeUndefined();
66
+ expect(testcase.runtimeContext?.testProjectDir).toBe(projectDir);
67
+ expect(testcase.runtimeContext?.caseDir).toBe(testsDir);
68
+ expect(testcase.runtimeContext?.specPath).toBe(path.join(testsDir, 'crud.spec.ts'));
69
+ expect(testcase.testAccess?.credentialRef).toBe('credentials.json5');
70
+ expect(testcase.testAccess?.requiredRoles).toEqual([
71
+ '业务管理员',
72
+ '领导/管理者',
73
+ '群众',
74
+ ]);
75
+ const allCaseIds = [
76
+ ...testcase.cases.map((item) => item.caseId),
77
+ ...testcase.uncoveredCandidates.map((item) => item.caseId),
78
+ ];
79
+ expect(allCaseIds).toEqual([
80
+ 'TC01001',
81
+ 'TC01002',
82
+ 'TC01003',
83
+ 'TC01004',
84
+ 'TC01005',
85
+ 'TC01006',
86
+ 'TC01007',
87
+ 'TC01008',
88
+ 'TC01009',
89
+ 'TC01010',
90
+ 'TC01011',
91
+ 'TC01012',
92
+ 'TC01013',
93
+ 'TC01014',
94
+ 'TC01015',
95
+ 'TC01016',
96
+ ]);
97
+ expect(new Set(allCaseIds).size).toBe(allCaseIds.length);
98
+ expect(allCaseIds.every((caseId) => /^TC\d{5}$/.test(caseId))).toBe(true);
99
+ expect(testcase.cases[0]?.queryKey).toBeTruthy();
100
+ expect(testcase.cases[0]?.steps).toContain('新增');
101
+ expect(testcase.cases[0]?.steps).toContain('点击{{新增按钮}}');
102
+ expect(testcase.cases[1]?.steps).toContain('点击{{查询按钮}}');
103
+ expect(testcase.cases[2]?.steps).toContain('点击{{修改按钮}}');
104
+ expect(testcase.cases[3]?.steps).toContain('点击{{删除按钮}}');
105
+ expect(testcase.cases[4]?.steps).toContain('选择首个有效下拉选项');
106
+ expect(testcase.cases[4]?.steps).not.toContain('带时间戳的测试值');
107
+ expect(testcase.cases[8]?.queryKey).toBe('状态');
108
+ expect(testcase.cases[8]?.precondition).not.toContain('已基于状态完成一条测试数据新增');
109
+ expect(testcase.cases[8]?.assertion).toContain('不强制断言列表数据命中数量');
110
+ expect(testcase.cases[9]?.steps).toContain('点击{{导出按钮}}');
111
+ expect(testcase.cases[0]?.steps).not.toBe(testcase.cases[1]?.steps);
112
+ expect(testcase.cases.map((item) => item.steps).join('\n')).not.toContain('按查询主键完成新增、查询、编辑、删除或导出流程');
113
+ expect(testcase.cases.map((item) => item.assertion).join('\n')).not.toContain('按查询主键、列表结果或导出行为验证执行结果');
114
+ expect(testcase.uncoveredCandidates.every((c) => c.generationStrategy === 'ai')).toBe(true);
115
+ expect(testcase.uncoveredCandidates[0]?.steps).toContain('由 AI Runtime Task 观察页面当前状态');
116
+ const testcaseMarkdown = fs.readFileSync(path.join(testsDir, 'testcase.md'), 'utf8');
117
+ expect(testcaseMarkdown).toContain('| 用例 ID | 生成策略 | 用例标题 | 查询主键 | 前置条件 | 执行步骤 | 执行断言 |');
118
+ expect(testcaseMarkdown).not.toContain('## 已覆盖用例');
119
+ expect(testcaseMarkdown).not.toContain('## 未覆盖候选');
120
+ }
121
+ finally {
122
+ homedirSpy.mockRestore();
123
+ }
124
+ });
125
+ it('缺少 directUrl 时阻断,避免产出不可执行 contract', () => {
126
+ const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ep-stage-home-'));
127
+ const testsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ep-stage-json5-'));
128
+ const projectDir = path.join(testsDir, 'glue-project');
129
+ const homedirSpy = vi.spyOn(os, 'homedir').mockReturnValue(homeDir);
130
+ try {
131
+ writeProjectIndex({
132
+ homeDir,
133
+ projects: [
134
+ {
135
+ projectName: 'glue-project',
136
+ projectDir,
137
+ mode: 'glue',
138
+ stageCreateVersion: 'test',
139
+ requirements: [],
140
+ },
141
+ ],
142
+ systems: [],
143
+ });
144
+ expect(() => runGenerateTestcase({
145
+ codeList: REPO_CODE_LIST,
146
+ testsDir,
147
+ projectDir,
148
+ })).toThrow(/缺少待测页面 directUrl/);
149
+ }
150
+ finally {
151
+ homedirSpy.mockRestore();
152
+ }
153
+ });
154
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../test/cli/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * src/cli/index.ts 路由测试(REQ-CLI-02 修订)。
3
+ *
4
+ * 覆盖:
5
+ * - --help / -h 打印 testcase / spec / run 3 子命令且不含 contract
6
+ * - 未知子命令 → process.exit(1)
7
+ * - 路由到 parseGenerateTestcaseArgs / parseSpecArgs / parseRunArgs
8
+ *
9
+ * 用 vi.mock 替换 generate-testcase / spec / run 模块,避免占位 throw 干扰路由判定。
10
+ */
11
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
12
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, symlinkSync } from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+ const generateTestcase = vi.hoisted(() => ({
17
+ parseGenerateTestcaseArgs: vi.fn((argv) => ({ codeList: 'parsed', testsDir: argv.join(' ') })),
18
+ runGenerateTestcase: vi.fn(() => 'TESTCASE_OK'),
19
+ }));
20
+ const spec = vi.hoisted(() => ({
21
+ parseSpecArgs: vi.fn((argv) => ({ testcase: 'parsed', out: argv.join(' ') })),
22
+ runSpec: vi.fn(() => 'SPEC_OK'),
23
+ }));
24
+ const run = vi.hoisted(() => ({
25
+ parseRunArgs: vi.fn((argv) => ({ testcase: argv.join(' '), headless: true })),
26
+ runRun: vi.fn(async () => 'RUN_OK'),
27
+ }));
28
+ vi.mock('../../src/cli/generate-testcase.js', () => generateTestcase);
29
+ vi.mock('../../src/cli/spec.js', () => spec);
30
+ vi.mock('../../src/cli/run.js', () => run);
31
+ const { main, printHelp } = await import('../../src/cli/index.js');
32
+ describe('ep-stage-skill CLI 路由', () => {
33
+ let logSpy;
34
+ let errSpy;
35
+ let exitSpy;
36
+ beforeEach(() => {
37
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
38
+ errSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
39
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
40
+ throw new Error(`__exit_${code ?? 0}__`);
41
+ }));
42
+ });
43
+ afterEach(() => {
44
+ logSpy.mockRestore();
45
+ errSpy.mockRestore();
46
+ exitSpy.mockRestore();
47
+ vi.clearAllMocks();
48
+ });
49
+ it('--help 打印 3 子命令 testcase/spec/run 且不注册 contract 子命令', async () => {
50
+ await main(['node', 'cli', '--help']);
51
+ const printed = logSpy.mock.calls.flat().join('\n');
52
+ expect(printed).toMatch(/ep-stage-skill testcase /);
53
+ expect(printed).toMatch(/ep-stage-skill spec /);
54
+ expect(printed).toMatch(/ep-stage-skill run --testcase <path>/);
55
+ expect(printed).not.toMatch(/ep-stage-skill run --spec/);
56
+ // 反向断言:不存在 `ep-stage-skill contract` 子命令入口
57
+ expect(printed).not.toMatch(/ep-stage-skill contract\b/);
58
+ });
59
+ it('-h 等价于 --help', async () => {
60
+ await main(['node', 'cli', '-h']);
61
+ expect(logSpy.mock.calls.flat().join('\n')).toMatch(/testcase/);
62
+ });
63
+ it('无参数时打印 help(不 exit)', async () => {
64
+ await main(['node', 'cli']);
65
+ expect(logSpy.mock.calls.flat().join('\n')).toMatch(/testcase/);
66
+ expect(exitSpy).not.toHaveBeenCalled();
67
+ });
68
+ it('printHelp 不注册 contract 子命令', () => {
69
+ printHelp();
70
+ expect(logSpy.mock.calls.flat().join('\n')).not.toMatch(/ep-stage-skill contract\b/);
71
+ });
72
+ it('未知子命令 exit 1 + stderr', async () => {
73
+ await expect(main(['node', 'cli', 'unknown-cmd'])).rejects.toThrow(/__exit_1__/);
74
+ expect(errSpy.mock.calls.flat().join('\n')).toMatch(/未知子命令/);
75
+ });
76
+ it('testcase 子命令路由到 runGenerateTestcase', async () => {
77
+ const result = await main(['node', 'cli', 'testcase', '--code-list', '/x']);
78
+ expect(generateTestcase.parseGenerateTestcaseArgs).toHaveBeenCalledWith(['--code-list', '/x']);
79
+ expect(generateTestcase.runGenerateTestcase).toHaveBeenCalled();
80
+ expect(result).toBe('TESTCASE_OK');
81
+ });
82
+ it('spec 子命令路由到 runSpec', async () => {
83
+ const result = await main(['node', 'cli', 'spec', '--testcase', '/t', '--out', '/o']);
84
+ expect(spec.parseSpecArgs).toHaveBeenCalledWith(['--testcase', '/t', '--out', '/o']);
85
+ expect(spec.runSpec).toHaveBeenCalled();
86
+ expect(result).toBe('SPEC_OK');
87
+ });
88
+ it('run 子命令路由到 runRun', async () => {
89
+ const result = await main(['node', 'cli', 'run', '--testcase', '/t']);
90
+ expect(run.parseRunArgs).toHaveBeenCalledWith(['--testcase', '/t']);
91
+ expect(run.runRun).toHaveBeenCalled();
92
+ expect(result).toBe('RUN_OK');
93
+ });
94
+ it('-- 分隔符被忽略', async () => {
95
+ await main(['node', 'cli', 'testcase', '--', '--code-list', '/x']);
96
+ expect(generateTestcase.parseGenerateTestcaseArgs).toHaveBeenCalledWith(['--code-list', '/x']);
97
+ });
98
+ it('软链 CLI 入口也应识别为直接执行', async () => {
99
+ const mod = await import('../../src/cli/index.js');
100
+ const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-skill-cli-'));
101
+ const realEntry = path.join(tmpDir, 'dist', 'src', 'cli', 'index.js');
102
+ const binEntry = path.join(tmpDir, '.bin', 'ep-stage-skill');
103
+ try {
104
+ mkdirSync(path.dirname(realEntry), { recursive: true });
105
+ mkdirSync(path.dirname(binEntry), { recursive: true });
106
+ writeFileSync(realEntry, '// mock cli entry\n', 'utf8');
107
+ symlinkSync(realEntry, binEntry);
108
+ expect(mod.isDirectExecution(pathToFileURL(realEntry).href, binEntry)).toBe(true);
109
+ }
110
+ finally {
111
+ rmSync(tmpDir, { recursive: true, force: true });
112
+ }
113
+ });
114
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.test.d.ts","sourceRoot":"","sources":["../../../test/cli/run.test.ts"],"names":[],"mappings":""}