@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.
- package/SKILL.md +27 -27
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -186
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -199
- 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 -0
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +292 -249
- 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 +18 -51
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +127 -131
- package/dist/src/cli/index.d.ts +11 -2
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +49 -16
- package/dist/src/cli/probe.d.ts +2 -0
- package/dist/src/cli/probe.d.ts.map +1 -1
- package/dist/src/cli/probe.js +239 -13
- 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 +494 -65
- package/dist/src/cli/spec.d.ts +18 -11
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +74 -20
- 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/inference-trace.d.ts +223 -0
- package/dist/src/contracts/inference-trace.d.ts.map +1 -0
- package/dist/src/contracts/inference-trace.js +12 -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/code-list.d.ts +3 -3
- package/dist/src/extractors/code-list.d.ts.map +1 -1
- package/dist/src/extractors/code-list.js +46 -11
- 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 +656 -238
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/inference/module-hints-inference.d.ts +30 -0
- package/dist/src/inference/module-hints-inference.d.ts.map +1 -0
- package/dist/src/inference/module-hints-inference.js +363 -0
- package/dist/src/inference/source-analysis.d.ts +26 -0
- package/dist/src/inference/source-analysis.d.ts.map +1 -0
- package/dist/src/inference/source-analysis.js +258 -0
- 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 +2 -0
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +2 -0
- package/dist/src/validation/inference-trace.d.ts +13 -0
- package/dist/src/validation/inference-trace.d.ts.map +1 -0
- package/dist/src/validation/inference-trace.js +31 -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/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.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 -93
- 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.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/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 -819
- 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/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 -141
- 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 -461
- 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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ep-stage-testcase/v1-draft 类型定义。
|
|
3
|
+
*
|
|
4
|
+
* 字段补齐 spec 门禁 + run 渲染所需 P1 字段:
|
|
5
|
+
* - cases[].reviewStatus:spec 门禁用
|
|
6
|
+
* - uncoveredCandidates[].{requiredRole,businessIntent,assertionExpectation}:run 追加渲染用
|
|
7
|
+
*
|
|
8
|
+
* 桶位约束:
|
|
9
|
+
* - cases[] 只承载 generationStrategy='glue'
|
|
10
|
+
* - uncoveredCandidates[] 只承载 generationStrategy='ai'
|
|
11
|
+
* 校验函数负责拒绝违规。
|
|
12
|
+
*/
|
|
13
|
+
import type { RuntimeContext } from './runtime-context.js';
|
|
14
|
+
import type { TestAccessConfig } from './test-access.js';
|
|
15
|
+
import type { TestcaseExecutionFields } from './testcase-execution-fields.js';
|
|
16
|
+
/**
|
|
17
|
+
* 用例生成策略(与 contract 层 GenerationStrategy 对齐,本层独立声明避免跨层依赖)。
|
|
18
|
+
*/
|
|
19
|
+
export type TestcaseGenerationStrategy = 'glue' | 'ai';
|
|
20
|
+
/**
|
|
21
|
+
* 用例覆盖状态。
|
|
22
|
+
*/
|
|
23
|
+
export type TestcaseCoverageStatus = 'covered' | 'partial' | 'gap' | 'blocked';
|
|
24
|
+
/**
|
|
25
|
+
* 用例评审状态(spec 门禁消费)。
|
|
26
|
+
*/
|
|
27
|
+
export type TestcaseReviewStatus = 'confirmed' | 'needs_review' | 'blocked';
|
|
28
|
+
/**
|
|
29
|
+
* testcase.json5 v1-draft 顶层类型。
|
|
30
|
+
*/
|
|
31
|
+
export type EpStageTestcaseV1Draft = {
|
|
32
|
+
schemaVersion: 'ep-stage-testcase/v1-draft';
|
|
33
|
+
testcaseId: string;
|
|
34
|
+
contractRef: string;
|
|
35
|
+
scenario: string;
|
|
36
|
+
moduleId: string;
|
|
37
|
+
moduleName: string;
|
|
38
|
+
menuPath?: string;
|
|
39
|
+
runtimeContext?: RuntimeContext;
|
|
40
|
+
testAccess?: TestAccessConfig;
|
|
41
|
+
cases: Array<{
|
|
42
|
+
caseId: string;
|
|
43
|
+
title: string;
|
|
44
|
+
generationStrategy: 'glue';
|
|
45
|
+
coverageStatus: TestcaseCoverageStatus;
|
|
46
|
+
reviewStatus: TestcaseReviewStatus;
|
|
47
|
+
} & TestcaseExecutionFields>;
|
|
48
|
+
uncoveredCandidates: Array<{
|
|
49
|
+
caseId: string;
|
|
50
|
+
label: string;
|
|
51
|
+
generationStrategy: 'ai';
|
|
52
|
+
coverageStatus: TestcaseCoverageStatus;
|
|
53
|
+
requiredRole: string;
|
|
54
|
+
businessIntent: string;
|
|
55
|
+
assertionExpectation: string;
|
|
56
|
+
} & TestcaseExecutionFields>;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=testcase-v1-draft.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testcase-v1-draft.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-v1-draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,IAAI,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,cAAc,GAAG,SAAS,CAAC;AAE5E;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,4BAA4B,CAAC;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,KAAK,EAAE,KAAK,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,kBAAkB,EAAE,MAAM,CAAC;QAC3B,cAAc,EAAE,sBAAsB,CAAC;QACvC,YAAY,EAAE,oBAAoB,CAAC;KACpC,GAAG,uBAAuB,CAAC,CAAC;IAC7B,mBAAmB,EAAE,KAAK,CAAC;QACzB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,kBAAkB,EAAE,IAAI,CAAC;QACzB,cAAc,EAAE,sBAAsB,CAAC;QACvC,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;KAC9B,GAAG,uBAAuB,CAAC,CAAC;CAC9B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ep-stage-testcase/v1-draft 类型定义。
|
|
3
|
+
*
|
|
4
|
+
* 字段补齐 spec 门禁 + run 渲染所需 P1 字段:
|
|
5
|
+
* - cases[].reviewStatus:spec 门禁用
|
|
6
|
+
* - uncoveredCandidates[].{requiredRole,businessIntent,assertionExpectation}:run 追加渲染用
|
|
7
|
+
*
|
|
8
|
+
* 桶位约束:
|
|
9
|
+
* - cases[] 只承载 generationStrategy='glue'
|
|
10
|
+
* - uncoveredCandidates[] 只承载 generationStrategy='ai'
|
|
11
|
+
* 校验函数负责拒绝违规。
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EpStageContractV1Draft } from '../contracts/glue-contract-v1.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验 contract.json5 v1-draft schema。
|
|
4
|
+
*
|
|
5
|
+
* 必含 schemaVersion:"ep-stage-contract/v1-draft" + contractId + contractKind +
|
|
6
|
+
* base.{module,navigation,credentials} + scenario.{searchFields[],skeletonSlots,gapCandidates[]} +
|
|
7
|
+
* unresolvedSlots[]。
|
|
8
|
+
*
|
|
9
|
+
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
10
|
+
* @returns 归一化后的 EpStageContractV1Draft。
|
|
11
|
+
* @throws 缺字段 / 版本不匹配 / 桶位结构错时抛结构化错误。
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateContract(obj: unknown): EpStageContractV1Draft;
|
|
14
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/validation/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAsB,MAAM,kCAAkC,CAAC;AAcnG;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAuHrE"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const ALLOWED_STRATEGIES = new Set(['glue', 'ai']);
|
|
2
|
+
/**
|
|
3
|
+
* 断言值为非空字符串,否则抛错。
|
|
4
|
+
*
|
|
5
|
+
* @param value - 待校验值。
|
|
6
|
+
* @param field - 字段路径(错误信息用)。
|
|
7
|
+
*/
|
|
8
|
+
function requireString(value, field) {
|
|
9
|
+
if (typeof value !== 'string' || !value)
|
|
10
|
+
throw new Error(`contract.json5 字段 ${field} 必须为非空字符串`);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 校验 contract.json5 v1-draft schema。
|
|
14
|
+
*
|
|
15
|
+
* 必含 schemaVersion:"ep-stage-contract/v1-draft" + contractId + contractKind +
|
|
16
|
+
* base.{module,navigation,credentials} + scenario.{searchFields[],skeletonSlots,gapCandidates[]} +
|
|
17
|
+
* unresolvedSlots[]。
|
|
18
|
+
*
|
|
19
|
+
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
20
|
+
* @returns 归一化后的 EpStageContractV1Draft。
|
|
21
|
+
* @throws 缺字段 / 版本不匹配 / 桶位结构错时抛结构化错误。
|
|
22
|
+
*/
|
|
23
|
+
export function validateContract(obj) {
|
|
24
|
+
if (!obj || typeof obj !== 'object')
|
|
25
|
+
throw new Error('contract.json5 顶层必须是对象');
|
|
26
|
+
const raw = obj;
|
|
27
|
+
if (raw.schemaVersion !== 'ep-stage-contract/v1-draft') {
|
|
28
|
+
throw new Error(`contract.json5 schemaVersion 必须为 ep-stage-contract/v1-draft,实际: ${String(raw.schemaVersion)}`);
|
|
29
|
+
}
|
|
30
|
+
requireString(raw.contractId, 'contractId');
|
|
31
|
+
requireString(raw.contractKind, 'contractKind');
|
|
32
|
+
// base.{module,navigation,credentials}
|
|
33
|
+
const base = raw.base;
|
|
34
|
+
if (!base || typeof base !== 'object')
|
|
35
|
+
throw new Error('contract.json5 缺 base');
|
|
36
|
+
const baseObj = base;
|
|
37
|
+
const moduleObj = baseObj.module;
|
|
38
|
+
if (!moduleObj || typeof moduleObj !== 'object')
|
|
39
|
+
throw new Error('contract.json5 缺 base.module');
|
|
40
|
+
const moduleRec = moduleObj;
|
|
41
|
+
requireString(moduleRec.id, 'base.module.id');
|
|
42
|
+
requireString(moduleRec.label, 'base.module.label');
|
|
43
|
+
const navigation = baseObj.navigation;
|
|
44
|
+
if (!navigation || typeof navigation !== 'object')
|
|
45
|
+
throw new Error('contract.json5 缺 base.navigation');
|
|
46
|
+
const navRec = navigation;
|
|
47
|
+
if (navRec.entryMode !== 'directUrl' && navRec.entryMode !== 'menu') {
|
|
48
|
+
throw new Error('contract.json5 base.navigation.entryMode 必须为 directUrl 或 menu');
|
|
49
|
+
}
|
|
50
|
+
if (navRec.entryMode === 'directUrl') {
|
|
51
|
+
requireString(navRec.directUrl, 'base.navigation.directUrl');
|
|
52
|
+
}
|
|
53
|
+
const credentials = baseObj.credentials;
|
|
54
|
+
if (!credentials || typeof credentials !== 'object')
|
|
55
|
+
throw new Error('contract.json5 缺 base.credentials');
|
|
56
|
+
requireString(credentials.source, 'base.credentials.source');
|
|
57
|
+
// scenario
|
|
58
|
+
const scenario = raw.scenario;
|
|
59
|
+
if (!scenario || typeof scenario !== 'object')
|
|
60
|
+
throw new Error('contract.json5 缺 scenario');
|
|
61
|
+
const sc = scenario;
|
|
62
|
+
requireString(sc.kind, 'scenario.kind');
|
|
63
|
+
// scenario.searchFields[]
|
|
64
|
+
if (!Array.isArray(sc.searchFields) || sc.searchFields.length === 0) {
|
|
65
|
+
throw new Error('contract.json5 缺 scenario.searchFields[]');
|
|
66
|
+
}
|
|
67
|
+
for (const [i, sf] of sc.searchFields.entries()) {
|
|
68
|
+
requireString(sf.field, `scenario.searchFields[${i}].field`);
|
|
69
|
+
requireString(sf.label, `scenario.searchFields[${i}].label`);
|
|
70
|
+
requireString(sf.component, `scenario.searchFields[${i}].component`);
|
|
71
|
+
}
|
|
72
|
+
// scenario.skeletonSlots.{crud,export}
|
|
73
|
+
const slots = sc.skeletonSlots;
|
|
74
|
+
if (!slots || typeof slots !== 'object')
|
|
75
|
+
throw new Error('contract.json5 缺 scenario.skeletonSlots');
|
|
76
|
+
const slotsRec = slots;
|
|
77
|
+
if (!slotsRec.crud || typeof slotsRec.crud !== 'object') {
|
|
78
|
+
throw new Error('contract.json5 缺 scenario.skeletonSlots.crud');
|
|
79
|
+
}
|
|
80
|
+
if (!slotsRec.export || typeof slotsRec.export !== 'object') {
|
|
81
|
+
throw new Error('contract.json5 缺 scenario.skeletonSlots.export');
|
|
82
|
+
}
|
|
83
|
+
if (!slotsRec.export || typeof slotsRec.export !== 'object') {
|
|
84
|
+
throw new Error('contract.json5 缺 scenario.skeletonSlots.export');
|
|
85
|
+
}
|
|
86
|
+
const exportSlot = slotsRec.export;
|
|
87
|
+
requireString(exportSlot.moduleId, 'scenario.skeletonSlots.export.moduleId');
|
|
88
|
+
requireString(exportSlot.label, 'scenario.skeletonSlots.export.label');
|
|
89
|
+
requireString(exportSlot.mode, 'scenario.skeletonSlots.export.mode');
|
|
90
|
+
// scenario.skeletonSlots.childGrids(可选)
|
|
91
|
+
if (slotsRec.childGrids !== undefined) {
|
|
92
|
+
if (!Array.isArray(slotsRec.childGrids)) {
|
|
93
|
+
throw new Error('contract.json5 scenario.skeletonSlots.childGrids 必须是数组');
|
|
94
|
+
}
|
|
95
|
+
for (const [i, grid] of slotsRec.childGrids.entries()) {
|
|
96
|
+
if (!grid || typeof grid !== 'object') {
|
|
97
|
+
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}] 必须是对象`);
|
|
98
|
+
}
|
|
99
|
+
requireString(grid.gridId, `scenario.skeletonSlots.childGrids[${i}].gridId`);
|
|
100
|
+
requireString(grid.label, `scenario.skeletonSlots.childGrids[${i}].label`);
|
|
101
|
+
if (!Array.isArray(grid.columns)) {
|
|
102
|
+
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}].columns 必须是数组`);
|
|
103
|
+
}
|
|
104
|
+
for (const [j, col] of grid.columns.entries()) {
|
|
105
|
+
if (!col || typeof col !== 'object') {
|
|
106
|
+
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}].columns[${j}] 必须是对象`);
|
|
107
|
+
}
|
|
108
|
+
requireString(col.field, `scenario.skeletonSlots.childGrids[${i}].columns[${j}].field`);
|
|
109
|
+
requireString(col.label, `scenario.skeletonSlots.childGrids[${i}].columns[${j}].label`);
|
|
110
|
+
if (col.component !== 'input' && col.component !== 'listbox') {
|
|
111
|
+
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}].columns[${j}].component 必须为 input 或 listbox`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// scenario.gapCandidates[]
|
|
117
|
+
if (!Array.isArray(sc.gapCandidates)) {
|
|
118
|
+
throw new Error('contract.json5 缺 scenario.gapCandidates[]');
|
|
119
|
+
}
|
|
120
|
+
for (const [i, g] of sc.gapCandidates.entries()) {
|
|
121
|
+
requireString(g.gapId, `scenario.gapCandidates[${i}].gapId`);
|
|
122
|
+
requireString(g.label, `scenario.gapCandidates[${i}].label`);
|
|
123
|
+
if (!ALLOWED_STRATEGIES.has(g.generationStrategy)) {
|
|
124
|
+
throw new Error(`scenario.gapCandidates[${i}].generationStrategy 必须 ∈ {glue,ai}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// unresolvedSlots[]
|
|
128
|
+
if (!Array.isArray(raw.unresolvedSlots)) {
|
|
129
|
+
throw new Error('contract.json5 缺 unresolvedSlots[]');
|
|
130
|
+
}
|
|
131
|
+
return raw;
|
|
132
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/** credentials.json5 单条凭据(REQ-ENV-03)。 */
|
|
2
2
|
export type Credential = {
|
|
3
|
-
|
|
3
|
+
/** 兼容旧字段,等价于 loginUrl。 */
|
|
4
|
+
url?: string;
|
|
5
|
+
/** 登录地址。 */
|
|
6
|
+
loginUrl?: string;
|
|
7
|
+
/** 待测页面直达链接。 */
|
|
8
|
+
directUrl?: string;
|
|
4
9
|
username: string;
|
|
5
10
|
password: string;
|
|
6
11
|
role?: string;
|
|
@@ -9,7 +14,7 @@ export type Credential = {
|
|
|
9
14
|
/**
|
|
10
15
|
* 校验 credentials.json5 数组形态(REQ-ENV-02 / REQ-ENV-03)。
|
|
11
16
|
*
|
|
12
|
-
* 不变量:数组至少 1 条;必填 url
|
|
17
|
+
* 不变量:数组至少 1 条;必填 loginUrl 或 url、username、password 非空;同 (loginUrl/url, role) 组合唯一。
|
|
13
18
|
*
|
|
14
19
|
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
15
20
|
* @returns 归一化后的凭据数组。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/validation/credentials.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/validation/credentials.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG;IACvB,0BAA0B;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,CAoC9D"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 校验 credentials.json5 数组形态(REQ-ENV-02 / REQ-ENV-03)。
|
|
3
3
|
*
|
|
4
|
-
* 不变量:数组至少 1 条;必填 url
|
|
4
|
+
* 不变量:数组至少 1 条;必填 loginUrl 或 url、username、password 非空;同 (loginUrl/url, role) 组合唯一。
|
|
5
5
|
*
|
|
6
6
|
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
7
7
|
* @returns 归一化后的凭据数组。
|
|
@@ -16,18 +16,29 @@ export function validateCredentials(obj) {
|
|
|
16
16
|
const result = [];
|
|
17
17
|
for (let i = 0; i < obj.length; i++) {
|
|
18
18
|
const e = obj[i];
|
|
19
|
-
for (const f of ['
|
|
19
|
+
for (const f of ['username', 'password']) {
|
|
20
20
|
if (typeof e[f] !== 'string' || !e[f].trim()) {
|
|
21
21
|
throw new Error(`credentials.json5 第 ${i + 1} 条缺必填字段 ${f}`);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
const loginUrl = typeof e.loginUrl === 'string' && e.loginUrl.trim()
|
|
25
|
+
? e.loginUrl
|
|
26
|
+
: e.url;
|
|
27
|
+
if (typeof loginUrl !== 'string' || !loginUrl.trim()) {
|
|
28
|
+
throw new Error(`credentials.json5 第 ${i + 1} 条缺必填字段 loginUrl`);
|
|
29
|
+
}
|
|
30
|
+
if (e.directUrl !== undefined && typeof e.directUrl !== 'string') {
|
|
31
|
+
throw new Error(`credentials.json5 第 ${i + 1} 条 directUrl 必须为字符串`);
|
|
32
|
+
}
|
|
24
33
|
const role = typeof e.role === 'string' ? e.role : '';
|
|
25
|
-
const key = `${
|
|
34
|
+
const key = `${loginUrl}|${role}`;
|
|
26
35
|
if (seen.has(key))
|
|
27
36
|
throw new Error(`credentials.json5 同 (url, role) 重复: ${key}`);
|
|
28
37
|
seen.add(key);
|
|
29
38
|
result.push({
|
|
30
|
-
|
|
39
|
+
loginUrl,
|
|
40
|
+
...(typeof e.url === 'string' ? { url: e.url } : {}),
|
|
41
|
+
...(typeof e.directUrl === 'string' ? { directUrl: e.directUrl } : {}),
|
|
31
42
|
username: e.username,
|
|
32
43
|
password: e.password,
|
|
33
44
|
...(role ? { role } : {}),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { validateProjectsIndex } from './projects-index.js';
|
|
2
2
|
export { validateTestcase } from './testcase.js';
|
|
3
|
+
export { validateContract } from './contract.js';
|
|
3
4
|
export { validateCredentials } from './credentials.js';
|
|
5
|
+
export { validateInferenceTrace } from './inference-trace.js';
|
|
4
6
|
export type { Credential } from './credentials.js';
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { validateProjectsIndex } from './projects-index.js';
|
|
2
2
|
export { validateTestcase } from './testcase.js';
|
|
3
|
+
export { validateContract } from './contract.js';
|
|
3
4
|
export { validateCredentials } from './credentials.js';
|
|
5
|
+
export { validateInferenceTrace } from './inference-trace.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { InferenceTraceV1 } from '../contracts/inference-trace.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验 inference-trace.json 的顶层结构(REQ-INFER-006)。
|
|
4
|
+
*
|
|
5
|
+
* 最小校验:只保证 schemaVersion 正确且 5 个必需顶层章节存在。
|
|
6
|
+
* 章节内部证据数组 / reason 允许扩展(spec §8),不做深校验。
|
|
7
|
+
*
|
|
8
|
+
* @param obj - 从 JSON.parse 得到的任意值。
|
|
9
|
+
* @returns 类型收窄后的 InferenceTraceV1。
|
|
10
|
+
* @throws 顶层非对象、schemaVersion 不符或缺章节时抛错。
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateInferenceTrace(obj: unknown): InferenceTraceV1;
|
|
13
|
+
//# sourceMappingURL=inference-trace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference-trace.d.ts","sourceRoot":"","sources":["../../../src/validation/inference-trace.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAoBrE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 校验 inference-trace.json 的顶层结构(REQ-INFER-006)。
|
|
3
|
+
*
|
|
4
|
+
* 最小校验:只保证 schemaVersion 正确且 5 个必需顶层章节存在。
|
|
5
|
+
* 章节内部证据数组 / reason 允许扩展(spec §8),不做深校验。
|
|
6
|
+
*
|
|
7
|
+
* @param obj - 从 JSON.parse 得到的任意值。
|
|
8
|
+
* @returns 类型收窄后的 InferenceTraceV1。
|
|
9
|
+
* @throws 顶层非对象、schemaVersion 不符或缺章节时抛错。
|
|
10
|
+
*/
|
|
11
|
+
export function validateInferenceTrace(obj) {
|
|
12
|
+
const raw = obj;
|
|
13
|
+
if (!raw || typeof raw !== 'object') {
|
|
14
|
+
throw new Error('inference-trace.json 顶层必须是对象');
|
|
15
|
+
}
|
|
16
|
+
if (raw.schemaVersion !== 'inference-trace/v1') {
|
|
17
|
+
throw new Error('inference-trace.json schemaVersion 必须是 inference-trace/v1');
|
|
18
|
+
}
|
|
19
|
+
for (const key of [
|
|
20
|
+
'materialInventory',
|
|
21
|
+
'sourceAnalysis',
|
|
22
|
+
'moduleHintsInference',
|
|
23
|
+
'coverageDiff',
|
|
24
|
+
'reasoningSummary',
|
|
25
|
+
]) {
|
|
26
|
+
if (!raw[key]) {
|
|
27
|
+
throw new Error(`inference-trace.json 缺 ${key}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EpStageTestcaseV1Draft } from '../testcase/testcase-v1-draft.js';
|
|
2
2
|
/**
|
|
3
|
-
* 校验 testcase.json5
|
|
3
|
+
* 校验 testcase.json5 v1-draft schema(新协议重构后唯一版本,破旧立新不保留 v2 兼容)。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 桶位约束:
|
|
6
|
+
* - cases[] 每项 generationStrategy 必须 === 'glue',且含 reviewStatus
|
|
7
|
+
* - uncoveredCandidates[] 每项 generationStrategy 必须 === 'ai',
|
|
8
|
+
* 且含 requiredRole/businessIntent/assertionExpectation
|
|
7
9
|
*
|
|
8
10
|
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
9
|
-
* @returns 归一化后的
|
|
10
|
-
* @throws 缺字段 / 版本不匹配 /
|
|
11
|
+
* @returns 归一化后的 EpStageTestcaseV1Draft。
|
|
12
|
+
* @throws 缺字段 / 版本不匹配 / 桶位违规时抛结构化错误。
|
|
11
13
|
*/
|
|
12
|
-
export declare function validateTestcase(obj: unknown):
|
|
14
|
+
export declare function validateTestcase(obj: unknown): EpStageTestcaseV1Draft;
|
|
13
15
|
//# sourceMappingURL=testcase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testcase.d.ts","sourceRoot":"","sources":["../../../src/validation/testcase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"testcase.d.ts","sourceRoot":"","sources":["../../../src/validation/testcase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,kCAAkC,CAAC;AA6I1C;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAwErE"}
|
|
@@ -1,53 +1,200 @@
|
|
|
1
|
-
const
|
|
1
|
+
const ALLOWED_COVERAGE = new Set([
|
|
2
|
+
'covered',
|
|
3
|
+
'partial',
|
|
4
|
+
'gap',
|
|
5
|
+
'blocked',
|
|
6
|
+
]);
|
|
7
|
+
const ALLOWED_REVIEW = new Set([
|
|
8
|
+
'confirmed',
|
|
9
|
+
'needs_review',
|
|
10
|
+
'blocked',
|
|
11
|
+
]);
|
|
2
12
|
/**
|
|
3
|
-
*
|
|
13
|
+
* 断言值为非空字符串,否则抛错。
|
|
4
14
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
15
|
+
* @param value - 待校验值。
|
|
16
|
+
* @param field - 字段路径(错误信息用)。
|
|
17
|
+
*/
|
|
18
|
+
function requireString(value, field) {
|
|
19
|
+
if (typeof value !== 'string' || !value)
|
|
20
|
+
throw new Error(`testcase.json5 字段 ${field} 必须为非空字符串`);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 断言值为字符串数组,否则抛错。
|
|
24
|
+
*
|
|
25
|
+
* @param value - 待校验值。
|
|
26
|
+
* @param field - 字段路径(错误信息用)。
|
|
27
|
+
*/
|
|
28
|
+
function requireStringArray(value, field) {
|
|
29
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || !item)) {
|
|
30
|
+
throw new Error(`testcase.json5 字段 ${field} 必须为非空字符串数组`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 断言值若存在则必须为字符串。
|
|
35
|
+
*
|
|
36
|
+
* @param value - 待校验值。
|
|
37
|
+
* @param field - 字段路径(错误信息用)。
|
|
38
|
+
*/
|
|
39
|
+
function requireOptionalString(value, field) {
|
|
40
|
+
if (value !== undefined && typeof value !== 'string') {
|
|
41
|
+
throw new Error(`testcase.json5 字段 ${field} 必须为字符串`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 校验可选执行字段。
|
|
46
|
+
*
|
|
47
|
+
* @param raw - 原始对象。
|
|
48
|
+
* @param fieldPrefix - 字段前缀。
|
|
49
|
+
*/
|
|
50
|
+
function validateExecutionFields(raw, fieldPrefix) {
|
|
51
|
+
requireOptionalString(raw.queryKey, `${fieldPrefix}.queryKey`);
|
|
52
|
+
requireOptionalString(raw.precondition, `${fieldPrefix}.precondition`);
|
|
53
|
+
requireOptionalString(raw.steps, `${fieldPrefix}.steps`);
|
|
54
|
+
requireOptionalString(raw.assertion, `${fieldPrefix}.assertion`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 校验 runtimeContext。
|
|
58
|
+
*
|
|
59
|
+
* @param value - 原始 runtimeContext。
|
|
60
|
+
*/
|
|
61
|
+
function validateRuntimeContext(value) {
|
|
62
|
+
if (!value || typeof value !== 'object') {
|
|
63
|
+
throw new Error('testcase.json5 字段 runtimeContext 必须为对象');
|
|
64
|
+
}
|
|
65
|
+
const runtimeContext = value;
|
|
66
|
+
requireString(runtimeContext.testProjectDir, 'runtimeContext.testProjectDir');
|
|
67
|
+
requireString(runtimeContext.caseDir, 'runtimeContext.caseDir');
|
|
68
|
+
requireString(runtimeContext.specPath, 'runtimeContext.specPath');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 校验 testAccess 角色。
|
|
72
|
+
*
|
|
73
|
+
* @param role - 角色配置。
|
|
74
|
+
* @param fieldPrefix - 字段前缀。
|
|
75
|
+
*/
|
|
76
|
+
function validateTestAccessRole(role, fieldPrefix) {
|
|
77
|
+
requireString(role.role, `${fieldPrefix}.role`);
|
|
78
|
+
requireOptionalString(role.username, `${fieldPrefix}.username`);
|
|
79
|
+
requireOptionalString(role.password, `${fieldPrefix}.password`);
|
|
80
|
+
requireStringArray(role.permissions, `${fieldPrefix}.permissions`);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 校验 testAccess 系统。
|
|
84
|
+
*
|
|
85
|
+
* @param system - 系统配置。
|
|
86
|
+
* @param fieldPrefix - 字段前缀。
|
|
87
|
+
*/
|
|
88
|
+
function validateTestAccessSystem(system, fieldPrefix) {
|
|
89
|
+
requireString(system.systemName, `${fieldPrefix}.systemName`);
|
|
90
|
+
requireOptionalString(system.loginUrl, `${fieldPrefix}.loginUrl`);
|
|
91
|
+
if (!Array.isArray(system.roles)) {
|
|
92
|
+
throw new Error(`testcase.json5 字段 ${fieldPrefix}.roles 必须为数组`);
|
|
93
|
+
}
|
|
94
|
+
for (const [index, role] of system.roles.entries()) {
|
|
95
|
+
if (!role || typeof role !== 'object') {
|
|
96
|
+
throw new Error(`testcase.json5 字段 ${fieldPrefix}.roles[${index}] 必须为对象`);
|
|
97
|
+
}
|
|
98
|
+
validateTestAccessRole(role, `${fieldPrefix}.roles[${index}]`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 校验 testAccess。
|
|
103
|
+
*
|
|
104
|
+
* @param value - 原始 testAccess。
|
|
105
|
+
*/
|
|
106
|
+
function validateTestAccess(value) {
|
|
107
|
+
if (!value || typeof value !== 'object') {
|
|
108
|
+
throw new Error('testcase.json5 字段 testAccess 必须为对象');
|
|
109
|
+
}
|
|
110
|
+
const testAccess = value;
|
|
111
|
+
requireOptionalString(testAccess.credentialRef, 'testAccess.credentialRef');
|
|
112
|
+
if (testAccess.requiredRoles !== undefined) {
|
|
113
|
+
requireStringArray(testAccess.requiredRoles, 'testAccess.requiredRoles');
|
|
114
|
+
}
|
|
115
|
+
if (testAccess.systems === undefined) {
|
|
116
|
+
if (testAccess.credentialRef === undefined) {
|
|
117
|
+
throw new Error('testcase.json5 字段 testAccess.credentialRef 或 testAccess.systems 必须至少提供一个');
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!Array.isArray(testAccess.systems)) {
|
|
122
|
+
throw new Error('testcase.json5 字段 testAccess.systems 必须为数组');
|
|
123
|
+
}
|
|
124
|
+
for (const [index, system] of testAccess.systems.entries()) {
|
|
125
|
+
if (!system || typeof system !== 'object') {
|
|
126
|
+
throw new Error(`testcase.json5 字段 testAccess.systems[${index}] 必须为对象`);
|
|
127
|
+
}
|
|
128
|
+
validateTestAccessSystem(system, `testAccess.systems[${index}]`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 校验 testcase.json5 v1-draft schema(新协议重构后唯一版本,破旧立新不保留 v2 兼容)。
|
|
133
|
+
*
|
|
134
|
+
* 桶位约束:
|
|
135
|
+
* - cases[] 每项 generationStrategy 必须 === 'glue',且含 reviewStatus
|
|
136
|
+
* - uncoveredCandidates[] 每项 generationStrategy 必须 === 'ai',
|
|
137
|
+
* 且含 requiredRole/businessIntent/assertionExpectation
|
|
7
138
|
*
|
|
8
139
|
* @param obj - 从 JSON5 解析出来的原始对象。
|
|
9
|
-
* @returns 归一化后的
|
|
10
|
-
* @throws 缺字段 / 版本不匹配 /
|
|
140
|
+
* @returns 归一化后的 EpStageTestcaseV1Draft。
|
|
141
|
+
* @throws 缺字段 / 版本不匹配 / 桶位违规时抛结构化错误。
|
|
11
142
|
*/
|
|
12
143
|
export function validateTestcase(obj) {
|
|
13
144
|
if (!obj || typeof obj !== 'object')
|
|
14
145
|
throw new Error('testcase.json5 顶层必须是对象');
|
|
15
146
|
const raw = obj;
|
|
16
|
-
if (raw.schemaVersion !== '
|
|
17
|
-
throw new Error(
|
|
147
|
+
if (raw.schemaVersion !== 'ep-stage-testcase/v1-draft') {
|
|
148
|
+
throw new Error(`testcase.json5 schemaVersion 必须为 ep-stage-testcase/v1-draft,实际: ${String(raw.schemaVersion)}`);
|
|
18
149
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
150
|
+
// 顶层字符串字段
|
|
151
|
+
for (const f of ['testcaseId', 'contractRef', 'scenario', 'moduleId', 'moduleName']) {
|
|
152
|
+
requireString(raw[f], f);
|
|
22
153
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
throw new Error(`testcase.json5 缺 ${f}[]`);
|
|
154
|
+
if (raw.menuPath !== undefined) {
|
|
155
|
+
requireString(raw.menuPath, 'menuPath');
|
|
26
156
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (const a of raw.coveredActions) {
|
|
33
|
-
if (!Array.isArray(a.evidence))
|
|
34
|
-
throw new Error('coveredActions[].evidence 必须为数组');
|
|
35
|
-
a.evidence.forEach(checkEvidence);
|
|
157
|
+
if (raw.runtimeContext !== undefined) {
|
|
158
|
+
validateRuntimeContext(raw.runtimeContext);
|
|
159
|
+
}
|
|
160
|
+
if (raw.testAccess !== undefined) {
|
|
161
|
+
validateTestAccess(raw.testAccess);
|
|
36
162
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
163
|
+
// cases[]
|
|
164
|
+
if (!Array.isArray(raw.cases))
|
|
165
|
+
throw new Error('testcase.json5 缺 cases[]');
|
|
166
|
+
for (const [i, c] of raw.cases.entries()) {
|
|
167
|
+
requireString(c.caseId, `cases[${i}].caseId`);
|
|
168
|
+
requireString(c.title, `cases[${i}].title`);
|
|
169
|
+
if (c.generationStrategy !== 'glue') {
|
|
170
|
+
throw new Error(`cases[${i}].generationStrategy 必须为 glue(cases 桶只承载 glue),实际: ${String(c.generationStrategy)}`);
|
|
171
|
+
}
|
|
172
|
+
if (!ALLOWED_COVERAGE.has(c.coverageStatus)) {
|
|
173
|
+
throw new Error(`cases[${i}].coverageStatus 必须 ∈ {covered,partial,gap,blocked}`);
|
|
40
174
|
}
|
|
41
|
-
if (!
|
|
42
|
-
throw new Error(
|
|
43
|
-
|
|
175
|
+
if (!ALLOWED_REVIEW.has(c.reviewStatus)) {
|
|
176
|
+
throw new Error(`cases[${i}].reviewStatus 必须 ∈ {confirmed,needs_review,blocked}(spec 门禁用)`);
|
|
177
|
+
}
|
|
178
|
+
validateExecutionFields(c, `cases[${i}]`);
|
|
179
|
+
}
|
|
180
|
+
// uncoveredCandidates[]
|
|
181
|
+
if (!Array.isArray(raw.uncoveredCandidates)) {
|
|
182
|
+
throw new Error('testcase.json5 缺 uncoveredCandidates[]');
|
|
44
183
|
}
|
|
45
|
-
for (const
|
|
46
|
-
|
|
47
|
-
|
|
184
|
+
for (const [i, u] of raw.uncoveredCandidates.entries()) {
|
|
185
|
+
requireString(u.caseId, `uncoveredCandidates[${i}].caseId`);
|
|
186
|
+
requireString(u.label, `uncoveredCandidates[${i}].label`);
|
|
187
|
+
const strat = u.generationStrategy;
|
|
188
|
+
if (strat !== 'ai') {
|
|
189
|
+
throw new Error(`uncoveredCandidates[${i}].generationStrategy 必须为 ai(gap 桶不承载 glue/manual),实际: ${String(strat)}`);
|
|
190
|
+
}
|
|
191
|
+
if (!ALLOWED_COVERAGE.has(u.coverageStatus)) {
|
|
192
|
+
throw new Error(`uncoveredCandidates[${i}].coverageStatus 必须 ∈ {covered,partial,gap,blocked}`);
|
|
48
193
|
}
|
|
49
|
-
|
|
50
|
-
|
|
194
|
+
requireString(u.requiredRole, `uncoveredCandidates[${i}].requiredRole`);
|
|
195
|
+
requireString(u.businessIntent, `uncoveredCandidates[${i}].businessIntent`);
|
|
196
|
+
requireString(u.assertionExpectation, `uncoveredCandidates[${i}].assertionExpectation`);
|
|
197
|
+
validateExecutionFields(u, `uncoveredCandidates[${i}]`);
|
|
51
198
|
}
|
|
52
199
|
return raw;
|
|
53
200
|
}
|