@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.
- package/SKILL.md +27 -27
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -223
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
- package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -95
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
- package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
- package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
- package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
- package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +127 -166
- package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
- package/dist/src/cli/generate-testcase.d.ts +0 -1
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +1 -6
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +14 -38
- package/dist/src/cli/run.d.ts +0 -2
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +21 -70
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +0 -9
- package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
- package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.d.ts +1 -34
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.js +0 -1
- package/dist/src/extractors/html-page.d.ts +0 -9
- package/dist/src/extractors/html-page.d.ts.map +1 -1
- package/dist/src/extractors/html-page.js +2 -26
- package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
- package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +116 -449
- package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
- package/dist/src/runtime/ai-runtime-trace.js +0 -8
- package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
- package/dist/src/testcase/contract-testcase-assembler.js +75 -329
- package/dist/src/validation/contract.d.ts.map +1 -1
- package/dist/src/validation/contract.js +0 -29
- package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase-json5.test.js +154 -0
- package/dist/test/cli/index.test.d.ts +2 -0
- package/dist/test/cli/index.test.d.ts.map +1 -0
- package/dist/test/cli/index.test.js +114 -0
- package/dist/test/cli/run.test.d.ts +2 -0
- package/dist/test/cli/run.test.d.ts.map +1 -0
- package/dist/test/cli/run.test.js +429 -0
- package/dist/test/cli/spec.test.d.ts +2 -0
- package/dist/test/cli/spec.test.d.ts.map +1 -0
- package/dist/test/cli/spec.test.js +112 -0
- package/dist/test/code-list-json5.test.d.ts +2 -0
- package/dist/test/code-list-json5.test.d.ts.map +1 -0
- package/dist/test/code-list-json5.test.js +84 -0
- package/dist/test/code-list.test.d.ts +2 -0
- package/dist/test/code-list.test.d.ts.map +1 -0
- package/dist/test/code-list.test.js +39 -0
- package/dist/test/gap-inference.test.d.ts +2 -0
- package/dist/test/gap-inference.test.d.ts.map +1 -0
- package/dist/test/gap-inference.test.js +597 -0
- package/dist/test/generalization.test.d.ts +2 -0
- package/dist/test/generalization.test.d.ts.map +1 -0
- package/dist/test/generalization.test.js +73 -0
- package/dist/test/material-inventory.test.d.ts +2 -0
- package/dist/test/material-inventory.test.d.ts.map +1 -0
- package/dist/test/material-inventory.test.js +155 -0
- package/dist/test/observable-chain.test.d.ts +2 -0
- package/dist/test/observable-chain.test.d.ts.map +1 -0
- package/dist/test/observable-chain.test.js +123 -0
- package/dist/test/page-structure.test.d.ts +2 -0
- package/dist/test/page-structure.test.d.ts.map +1 -0
- package/dist/test/page-structure.test.js +45 -0
- package/dist/test/runtime/ai-runtime-trace.test.d.ts +2 -0
- package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
- package/dist/test/runtime/ai-runtime-trace.test.js +78 -0
- package/dist/test/scenario-inference.test.d.ts +2 -0
- package/dist/test/scenario-inference.test.d.ts.map +1 -0
- package/dist/test/scenario-inference.test.js +73 -0
- package/dist/test/stage-context.test.d.ts +2 -0
- package/dist/test/stage-context.test.d.ts.map +1 -0
- package/dist/test/stage-context.test.js +395 -0
- package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
- package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
- package/dist/test/testcase/testcase-markdown.test.js +48 -0
- package/dist/test/testcase-generator.test.d.ts +2 -0
- package/dist/test/testcase-generator.test.d.ts.map +1 -0
- package/dist/test/testcase-generator.test.js +276 -0
- package/dist/test/testcase-skeleton.test.d.ts +2 -0
- package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
- package/dist/test/testcase-skeleton.test.js +185 -0
- package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
- package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
- package/dist/test/testcase-spec-assembly.test.js +105 -0
- package/dist/test/util/credentials.test.d.ts +2 -0
- package/dist/test/util/credentials.test.d.ts.map +1 -0
- package/dist/test/util/credentials.test.js +64 -0
- package/dist/test/util/i18n-testcase.test.d.ts +2 -0
- package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
- package/dist/test/util/i18n-testcase.test.js +119 -0
- package/dist/test/util/softlink.test.d.ts +2 -0
- package/dist/test/util/softlink.test.d.ts.map +1 -0
- package/dist/test/util/softlink.test.js +82 -0
- package/dist/test/validation/contract.test.d.ts +2 -0
- package/dist/test/validation/contract.test.d.ts.map +1 -0
- package/dist/test/validation/contract.test.js +116 -0
- package/dist/test/validation/credentials.test.d.ts +2 -0
- package/dist/test/validation/credentials.test.d.ts.map +1 -0
- package/dist/test/validation/credentials.test.js +74 -0
- package/dist/test/validation/projects-index.test.d.ts +2 -0
- package/dist/test/validation/projects-index.test.d.ts.map +1 -0
- package/dist/test/validation/projects-index.test.js +48 -0
- package/dist/test/validation/testcase-v1-draft.test.d.ts +2 -0
- package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
- package/dist/test/validation/testcase-v1-draft.test.js +173 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +7 -0
- package/docs/README.md +124 -124
- package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
- package/examples/schemeresource.module-hints.json +38 -38
- package/examples/schemeresource.observable.code_list.md +37 -37
- package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
- package/examples/zwplace-placecategory-validation.json +29 -29
- package/examples/zwplace.module-hints.json +69 -69
- package/examples/zwplace.observable.code_list.md +37 -37
- package/package.json +19 -13
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
- package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
- package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
- package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
- package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
- package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
- package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
- package/dist/src/cli/probe.d.ts +0 -46
- package/dist/src/cli/probe.d.ts.map +0 -1
- package/dist/src/cli/probe.js +0 -447
- package/dist/src/cli/run-report.d.ts +0 -46
- package/dist/src/cli/run-report.d.ts.map +0 -1
- package/dist/src/cli/run-report.js +0 -316
- package/dist/src/extractors/prd-extractor.d.ts +0 -70
- package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
- package/dist/src/extractors/prd-extractor.js +0 -248
- package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
- package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
- package/dist/src/testcase/button-interaction-inference.js +0 -385
- package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
- package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
- package/dist/src/testcase/prd-button-mapper.js +0 -148
- package/dist/src/testcase/testcase-v2.d.ts +0 -50
- package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
- package/dist/src/testcase/testcase-v2.js +0 -1
- 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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"run.test.d.ts","sourceRoot":"","sources":["../../../test/cli/run.test.ts"],"names":[],"mappings":""}
|