@epoint-testtech/ep-stage-skill 0.0.4-alpha.1 → 0.0.6
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 -206
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -274
- 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 -112
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -72
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +295 -289
- package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -0
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -145
- package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -43
- 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 +166 -0
- package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +233 -0
- package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
- package/codex-skill/ep-stage/page-element-scan/SKILL.md +91 -0
- package/codex-skill/ep-stage/page-element-scan/evals/evals.json +37 -0
- package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +85 -0
- package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +153 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +233 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +298 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +749 -0
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +98 -95
- package/dist/src/cli/generate-testcase.d.ts +17 -51
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +74 -181
- package/dist/src/cli/index.d.ts +3 -2
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +51 -17
- package/dist/src/cli/run.d.ts +136 -29
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +507 -65
- package/dist/src/cli/spec.d.ts +17 -13
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +59 -33
- package/dist/src/contracts/crud-business-module.d.ts +6 -0
- package/dist/src/contracts/crud-business-module.d.ts.map +1 -1
- package/dist/src/contracts/glue-contract-v1.d.ts +98 -0
- package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -0
- package/dist/src/contracts/glue-contract-v1.js +9 -0
- package/dist/src/contracts/observable-chain.d.ts +4 -0
- package/dist/src/contracts/observable-chain.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.d.ts +192 -0
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -0
- package/dist/src/extractors/code-list-json5.js +91 -0
- package/dist/src/extractors/html-page.d.ts +9 -0
- package/dist/src/extractors/html-page.d.ts.map +1 -1
- package/dist/src/extractors/html-page.js +26 -2
- package/dist/src/extractors/prd-extractor.d.ts +70 -0
- package/dist/src/extractors/prd-extractor.d.ts.map +1 -0
- package/dist/src/extractors/prd-extractor.js +248 -0
- package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
- package/dist/src/generators/stage-skeleton-script.d.ts +87 -20
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +647 -238
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/normalizers/crud-contract.d.ts +3 -0
- package/dist/src/normalizers/crud-contract.d.ts.map +1 -1
- package/dist/src/normalizers/crud-contract.js +3 -0
- package/dist/src/runtime/ai-runtime-trace.d.ts +88 -0
- package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -0
- package/dist/src/runtime/ai-runtime-trace.js +119 -0
- package/dist/src/testcase/button-interaction-inference.d.ts +37 -0
- package/dist/src/testcase/button-interaction-inference.d.ts.map +1 -0
- package/dist/src/testcase/button-interaction-inference.js +385 -0
- package/dist/src/testcase/contract-testcase-assembler.d.ts +44 -0
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -0
- package/dist/src/testcase/contract-testcase-assembler.js +806 -0
- package/dist/src/testcase/prd-button-mapper.d.ts +72 -0
- package/dist/src/testcase/prd-button-mapper.d.ts.map +1 -0
- package/dist/src/testcase/prd-button-mapper.js +148 -0
- package/dist/src/testcase/runtime-context.d.ts +9 -0
- package/dist/src/testcase/runtime-context.d.ts.map +1 -0
- package/dist/src/testcase/runtime-context.js +1 -0
- package/dist/src/testcase/test-access.d.ts +38 -0
- package/dist/src/testcase/test-access.d.ts.map +1 -0
- package/dist/src/testcase/test-access.js +106 -0
- package/dist/src/testcase/testcase-execution-fields.d.ts +10 -0
- package/dist/src/testcase/testcase-execution-fields.d.ts.map +1 -0
- package/dist/src/testcase/testcase-execution-fields.js +1 -0
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
- package/dist/src/testcase/testcase-generator.js +6 -0
- package/dist/src/testcase/testcase-markdown.d.ts +11 -0
- package/dist/src/testcase/testcase-markdown.d.ts.map +1 -0
- package/dist/src/testcase/testcase-markdown.js +181 -0
- package/dist/src/testcase/testcase-skeleton.d.ts +4 -0
- package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -1
- package/dist/src/testcase/testcase-spec-assembly.d.ts +4 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -1
- package/dist/src/testcase/testcase-v1-draft.d.ts +58 -0
- package/dist/src/testcase/testcase-v1-draft.d.ts.map +1 -0
- package/dist/src/testcase/testcase-v1-draft.js +13 -0
- package/dist/src/validation/contract.d.ts +14 -0
- package/dist/src/validation/contract.d.ts.map +1 -0
- package/dist/src/validation/contract.js +132 -0
- package/dist/src/validation/credentials.d.ts +7 -2
- package/dist/src/validation/credentials.d.ts.map +1 -1
- package/dist/src/validation/credentials.js +15 -4
- package/dist/src/validation/index.d.ts +1 -0
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +1 -0
- package/dist/src/validation/testcase.d.ts +9 -7
- package/dist/src/validation/testcase.d.ts.map +1 -1
- package/dist/src/validation/testcase.js +181 -34
- package/docs/README.md +124 -134
- package/docs/mvp-usage-guide.md +298 -298
- 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 +2 -2
- package/codex-skill/ep-stage/.omc/state/sessions/d636ad35-5fc9-4d5e-85b0-2bcad7d86ee5/pre-tool-advisory-throttle.json +0 -10
- package/dist/test/cli/extract-contract.test.d.ts +0 -2
- package/dist/test/cli/extract-contract.test.d.ts.map +0 -1
- package/dist/test/cli/extract-contract.test.js +0 -32
- package/dist/test/cli/generate-testcase-inference.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase-inference.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase-inference.test.js +0 -63
- package/dist/test/cli/generate-testcase-paths.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase-paths.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase-paths.test.js +0 -165
- package/dist/test/cli/generate-testcase-requirements.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase-requirements.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase-requirements.test.js +0 -229
- package/dist/test/cli/generate-testcase.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase.test.js +0 -130
- package/dist/test/cli/index.test.d.ts +0 -2
- package/dist/test/cli/index.test.d.ts.map +0 -1
- package/dist/test/cli/index.test.js +0 -113
- package/dist/test/cli/probe.test.d.ts +0 -2
- package/dist/test/cli/probe.test.d.ts.map +0 -1
- package/dist/test/cli/probe.test.js +0 -31
- package/dist/test/cli/run.test.d.ts +0 -2
- package/dist/test/cli/run.test.d.ts.map +0 -1
- package/dist/test/cli/run.test.js +0 -149
- package/dist/test/cli/spec-boundary.test.d.ts +0 -2
- package/dist/test/cli/spec-boundary.test.d.ts.map +0 -1
- package/dist/test/cli/spec-boundary.test.js +0 -198
- package/dist/test/cli/spec.test.d.ts +0 -2
- package/dist/test/cli/spec.test.d.ts.map +0 -1
- package/dist/test/cli/spec.test.js +0 -196
- package/dist/test/code-list.test.d.ts +0 -2
- package/dist/test/code-list.test.d.ts.map +0 -1
- package/dist/test/code-list.test.js +0 -39
- package/dist/test/crud-contract.test.d.ts +0 -2
- package/dist/test/crud-contract.test.d.ts.map +0 -1
- package/dist/test/crud-contract.test.js +0 -799
- package/dist/test/gap-inference.test.d.ts +0 -2
- package/dist/test/gap-inference.test.d.ts.map +0 -1
- package/dist/test/gap-inference.test.js +0 -597
- package/dist/test/generalization.test.d.ts +0 -2
- package/dist/test/generalization.test.d.ts.map +0 -1
- package/dist/test/generalization.test.js +0 -73
- package/dist/test/inference/module-hints-inference.test.d.ts +0 -2
- package/dist/test/inference/module-hints-inference.test.d.ts.map +0 -1
- package/dist/test/inference/module-hints-inference.test.js +0 -25
- package/dist/test/inference/source-analysis.test.d.ts +0 -2
- package/dist/test/inference/source-analysis.test.d.ts.map +0 -1
- package/dist/test/inference/source-analysis.test.js +0 -23
- package/dist/test/material-inventory.test.d.ts +0 -2
- package/dist/test/material-inventory.test.d.ts.map +0 -1
- package/dist/test/material-inventory.test.js +0 -154
- package/dist/test/observable-chain.test.d.ts +0 -2
- package/dist/test/observable-chain.test.d.ts.map +0 -1
- package/dist/test/observable-chain.test.js +0 -123
- package/dist/test/observable-pipeline.test.d.ts +0 -2
- package/dist/test/observable-pipeline.test.d.ts.map +0 -1
- package/dist/test/observable-pipeline.test.js +0 -470
- package/dist/test/page-structure.test.d.ts +0 -2
- package/dist/test/page-structure.test.d.ts.map +0 -1
- package/dist/test/page-structure.test.js +0 -45
- package/dist/test/scenario-inference.test.d.ts +0 -2
- package/dist/test/scenario-inference.test.d.ts.map +0 -1
- package/dist/test/scenario-inference.test.js +0 -73
- package/dist/test/stage-context.test.d.ts +0 -2
- package/dist/test/stage-context.test.d.ts.map +0 -1
- package/dist/test/stage-context.test.js +0 -395
- package/dist/test/testcase-generator.test.d.ts +0 -2
- package/dist/test/testcase-generator.test.d.ts.map +0 -1
- package/dist/test/testcase-generator.test.js +0 -276
- package/dist/test/testcase-skeleton.test.d.ts +0 -2
- package/dist/test/testcase-skeleton.test.d.ts.map +0 -1
- package/dist/test/testcase-skeleton.test.js +0 -185
- package/dist/test/testcase-spec-assembly.test.d.ts +0 -2
- package/dist/test/testcase-spec-assembly.test.d.ts.map +0 -1
- package/dist/test/testcase-spec-assembly.test.js +0 -105
- package/dist/test/util/credentials.test.d.ts +0 -2
- package/dist/test/util/credentials.test.d.ts.map +0 -1
- package/dist/test/util/credentials.test.js +0 -64
- package/dist/test/util/i18n-testcase.test.d.ts +0 -2
- package/dist/test/util/i18n-testcase.test.d.ts.map +0 -1
- package/dist/test/util/i18n-testcase.test.js +0 -119
- package/dist/test/util/softlink.test.d.ts +0 -2
- package/dist/test/util/softlink.test.d.ts.map +0 -1
- package/dist/test/util/softlink.test.js +0 -82
- package/dist/test/validation/credentials.test.d.ts +0 -2
- package/dist/test/validation/credentials.test.d.ts.map +0 -1
- package/dist/test/validation/credentials.test.js +0 -72
- package/dist/test/validation/projects-index.test.d.ts +0 -2
- package/dist/test/validation/projects-index.test.d.ts.map +0 -1
- package/dist/test/validation/projects-index.test.js +0 -48
- package/dist/test/validation/testcase.test.d.ts +0 -2
- package/dist/test/validation/testcase.test.d.ts.map +0 -1
- package/dist/test/validation/testcase.test.js +0 -129
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- 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 +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 +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 +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 +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 +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
|
-
});
|