@unlaxer/tramli-plugins 3.1.0 → 3.3.0
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/dist/cjs/api/types.d.ts +24 -5
- package/dist/cjs/api/types.js +20 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/lint/default-flow-policies.js +4 -4
- package/dist/cjs/testing/scenario-test-plugin.d.ts +1 -0
- package/dist/cjs/testing/scenario-test-plugin.js +63 -1
- package/dist/cjs/testing/types.d.ts +2 -0
- package/dist/esm/api/types.d.ts +24 -5
- package/dist/esm/api/types.js +20 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/lint/default-flow-policies.js +4 -4
- package/dist/esm/testing/scenario-test-plugin.d.ts +1 -0
- package/dist/esm/testing/scenario-test-plugin.js +63 -1
- package/dist/esm/testing/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/cjs/api/types.d.ts
CHANGED
|
@@ -36,16 +36,35 @@ export interface GenerationPlugin<I, O> extends FlowPlugin {
|
|
|
36
36
|
/** Documentation plugin — generates string documentation. */
|
|
37
37
|
export interface DocumentationPlugin<I> extends GenerationPlugin<I, string> {
|
|
38
38
|
}
|
|
39
|
+
/** Describes where in a flow definition a finding is located. */
|
|
40
|
+
export type FindingLocation = {
|
|
41
|
+
type: 'transition';
|
|
42
|
+
fromState: string;
|
|
43
|
+
toState: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'state';
|
|
46
|
+
state: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'data';
|
|
49
|
+
dataKey: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'flow';
|
|
52
|
+
};
|
|
53
|
+
/** A single analysis finding. */
|
|
54
|
+
export interface FindingEntry {
|
|
55
|
+
pluginId: string;
|
|
56
|
+
severity: string;
|
|
57
|
+
message: string;
|
|
58
|
+
location?: FindingLocation;
|
|
59
|
+
}
|
|
39
60
|
/** Plugin report — collects analysis findings. */
|
|
40
61
|
export declare class PluginReport {
|
|
41
62
|
private entries;
|
|
42
63
|
add(pluginId: string, severity: string, message: string): void;
|
|
43
64
|
warn(pluginId: string, message: string): void;
|
|
44
65
|
error(pluginId: string, message: string): void;
|
|
66
|
+
warnAt(pluginId: string, message: string, location: FindingLocation): void;
|
|
67
|
+
errorAt(pluginId: string, message: string, location: FindingLocation): void;
|
|
45
68
|
asText(): string;
|
|
46
|
-
findings():
|
|
47
|
-
pluginId: string;
|
|
48
|
-
severity: string;
|
|
49
|
-
message: string;
|
|
50
|
-
}[];
|
|
69
|
+
findings(): FindingEntry[];
|
|
51
70
|
}
|
package/dist/cjs/api/types.js
CHANGED
|
@@ -13,11 +13,30 @@ class PluginReport {
|
|
|
13
13
|
error(pluginId, message) {
|
|
14
14
|
this.add(pluginId, 'ERROR', message);
|
|
15
15
|
}
|
|
16
|
+
warnAt(pluginId, message, location) {
|
|
17
|
+
this.entries.push({ pluginId, severity: 'WARN', message, location });
|
|
18
|
+
}
|
|
19
|
+
errorAt(pluginId, message, location) {
|
|
20
|
+
this.entries.push({ pluginId, severity: 'ERROR', message, location });
|
|
21
|
+
}
|
|
16
22
|
asText() {
|
|
17
23
|
if (this.entries.length === 0)
|
|
18
24
|
return 'No findings.';
|
|
19
|
-
return this.entries.map(e =>
|
|
25
|
+
return this.entries.map(e => {
|
|
26
|
+
let text = `[${e.severity}] ${e.pluginId}: ${e.message}`;
|
|
27
|
+
if (e.location)
|
|
28
|
+
text += ` @ ${formatLocation(e.location)}`;
|
|
29
|
+
return text;
|
|
30
|
+
}).join('\n');
|
|
20
31
|
}
|
|
21
32
|
findings() { return [...this.entries]; }
|
|
22
33
|
}
|
|
23
34
|
exports.PluginReport = PluginReport;
|
|
35
|
+
function formatLocation(loc) {
|
|
36
|
+
switch (loc.type) {
|
|
37
|
+
case 'transition': return `transition(${loc.fromState} -> ${loc.toState})`;
|
|
38
|
+
case 'state': return `state(${loc.state})`;
|
|
39
|
+
case 'data': return `data(${loc.dataKey})`;
|
|
40
|
+
case 'flow': return 'flow';
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { PluginReport } from './api/types.js';
|
|
2
|
-
export type { PluginKind, PluginDescriptor, FlowPlugin, AnalysisPlugin, StorePlugin, EnginePlugin, RuntimeAdapterPlugin, GenerationPlugin, DocumentationPlugin as DocumentationPluginSPI, } from './api/types.js';
|
|
2
|
+
export type { FindingLocation, FindingEntry, PluginKind, PluginDescriptor, FlowPlugin, AnalysisPlugin, StorePlugin, EnginePlugin, RuntimeAdapterPlugin, GenerationPlugin, DocumentationPlugin as DocumentationPluginSPI, } from './api/types.js';
|
|
3
3
|
export { PluginRegistry } from './api/plugin-registry.js';
|
|
4
4
|
export { AuditStorePlugin } from './audit/audit-store-plugin.js';
|
|
5
5
|
export { AuditingFlowStore } from './audit/auditing-flow-store.js';
|
|
@@ -32,5 +32,5 @@ export { allDefaultPolicies } from './lint/default-flow-policies.js';
|
|
|
32
32
|
export type { FlowPolicy } from './lint/types.js';
|
|
33
33
|
export { ScenarioTestPlugin } from './testing/scenario-test-plugin.js';
|
|
34
34
|
export { ScenarioGenerationPlugin } from './testing/scenario-generation-plugin.js';
|
|
35
|
-
export type { FlowScenario, FlowTestPlan } from './testing/types.js';
|
|
35
|
+
export type { FlowScenario, FlowTestPlan, ScenarioKind } from './testing/types.js';
|
|
36
36
|
export { GuaranteedSubflowValidator } from './subflow/guaranteed-subflow-validator.js';
|
|
@@ -4,7 +4,7 @@ exports.allDefaultPolicies = allDefaultPolicies;
|
|
|
4
4
|
function warnTerminalWithOutgoing(def, report) {
|
|
5
5
|
for (const state of def.terminalStates) {
|
|
6
6
|
if (def.transitionsFrom(state).length > 0) {
|
|
7
|
-
report.
|
|
7
|
+
report.warnAt('policy/terminal-outgoing', `terminal state ${state} has outgoing transitions`, { type: 'state', state });
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -12,7 +12,7 @@ function warnTooManyExternals(def, report) {
|
|
|
12
12
|
for (const state of def.allStates()) {
|
|
13
13
|
const externals = def.transitionsFrom(state).filter(t => t.type === 'external');
|
|
14
14
|
if (externals.length > 3) {
|
|
15
|
-
report.
|
|
15
|
+
report.warnAt('policy/external-count', `state ${state} has ${externals.length} external transitions`, { type: 'state', state });
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -21,13 +21,13 @@ function warnDeadProducedData(def, report) {
|
|
|
21
21
|
return;
|
|
22
22
|
const dead = def.dataFlowGraph.deadData();
|
|
23
23
|
for (const key of dead) {
|
|
24
|
-
report.
|
|
24
|
+
report.warnAt('policy/dead-data', `produced but never consumed: ${key}`, { type: 'data', dataKey: key });
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
function warnOverwideProcessors(def, report) {
|
|
28
28
|
for (const t of def.transitions) {
|
|
29
29
|
if (t.processor && t.processor.produces.length > 3) {
|
|
30
|
-
report.
|
|
30
|
+
report.warnAt('policy/overwide-processor', `${t.processor.name} produces ${t.processor.produces.length} types; consider splitting it`, { type: 'transition', fromState: t.from, toState: t.to });
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -2,6 +2,7 @@ import type { FlowDefinition } from '@unlaxer/tramli';
|
|
|
2
2
|
import type { FlowTestPlan } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates BDD-style test scenarios from a flow definition.
|
|
5
|
+
* Covers happy paths, error transitions, guard rejections, and timeout expiry.
|
|
5
6
|
*/
|
|
6
7
|
export declare class ScenarioTestPlugin {
|
|
7
8
|
generate<S extends string>(definition: FlowDefinition<S>): FlowTestPlan;
|
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ScenarioTestPlugin = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Generates BDD-style test scenarios from a flow definition.
|
|
6
|
+
* Covers happy paths, error transitions, guard rejections, and timeout expiry.
|
|
6
7
|
*/
|
|
7
8
|
class ScenarioTestPlugin {
|
|
8
9
|
generate(definition) {
|
|
9
10
|
const scenarios = [];
|
|
11
|
+
// Happy path scenarios from transitions
|
|
10
12
|
for (const t of definition.transitions) {
|
|
11
13
|
const steps = [];
|
|
12
14
|
steps.push(`given flow in ${t.from}`);
|
|
@@ -20,7 +22,67 @@ class ScenarioTestPlugin {
|
|
|
20
22
|
steps.push(`when branch ${t.branch.name} selects a route`);
|
|
21
23
|
}
|
|
22
24
|
steps.push(`then flow reaches ${t.to}`);
|
|
23
|
-
scenarios.push({ name: `${t.from}_to_${t.to}`, steps });
|
|
25
|
+
scenarios.push({ name: `${t.from}_to_${t.to}`, kind: 'happy', steps });
|
|
26
|
+
}
|
|
27
|
+
// Error path scenarios from errorTransitions
|
|
28
|
+
for (const [from, to] of definition.errorTransitions) {
|
|
29
|
+
scenarios.push({
|
|
30
|
+
name: `error_${from}_to_${to}`,
|
|
31
|
+
kind: 'error',
|
|
32
|
+
steps: [
|
|
33
|
+
`given flow in ${from}`,
|
|
34
|
+
`when processor throws an error`,
|
|
35
|
+
`then flow transitions to ${to} via on_error`,
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Exception route scenarios
|
|
40
|
+
if (definition.exceptionRoutes) {
|
|
41
|
+
for (const [from, routes] of definition.exceptionRoutes) {
|
|
42
|
+
for (const route of routes) {
|
|
43
|
+
const label = route.errorClass?.name ?? 'error';
|
|
44
|
+
scenarios.push({
|
|
45
|
+
name: `step_error_${from}_${label}_to_${route.target}`,
|
|
46
|
+
kind: 'error',
|
|
47
|
+
steps: [
|
|
48
|
+
`given flow in ${from}`,
|
|
49
|
+
`when error matching ${label} is thrown`,
|
|
50
|
+
`then flow transitions to ${route.target} via on_step_error`,
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Guard rejection scenarios
|
|
57
|
+
for (const t of definition.transitions) {
|
|
58
|
+
if (t.type === 'external' && t.guard) {
|
|
59
|
+
const errorTarget = definition.errorTransitions.get(t.from);
|
|
60
|
+
scenarios.push({
|
|
61
|
+
name: `guard_reject_${t.from}_${t.guard.name}`,
|
|
62
|
+
kind: 'guard_rejection',
|
|
63
|
+
steps: [
|
|
64
|
+
`given flow in ${t.from}`,
|
|
65
|
+
`when guard ${t.guard.name} rejects ${definition.maxGuardRetries} times`,
|
|
66
|
+
errorTarget
|
|
67
|
+
? `then flow transitions to ${errorTarget} via error`
|
|
68
|
+
: `then flow enters TERMINAL_ERROR`,
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Timeout scenarios
|
|
74
|
+
for (const t of definition.transitions) {
|
|
75
|
+
if (t.timeout != null) {
|
|
76
|
+
scenarios.push({
|
|
77
|
+
name: `timeout_${t.from}`,
|
|
78
|
+
kind: 'timeout',
|
|
79
|
+
steps: [
|
|
80
|
+
`given flow in ${t.from}`,
|
|
81
|
+
`when per-state timeout of ${t.timeout}ms expires`,
|
|
82
|
+
`then flow completes as EXPIRED`,
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
24
86
|
}
|
|
25
87
|
return { scenarios };
|
|
26
88
|
}
|
package/dist/esm/api/types.d.ts
CHANGED
|
@@ -36,16 +36,35 @@ export interface GenerationPlugin<I, O> extends FlowPlugin {
|
|
|
36
36
|
/** Documentation plugin — generates string documentation. */
|
|
37
37
|
export interface DocumentationPlugin<I> extends GenerationPlugin<I, string> {
|
|
38
38
|
}
|
|
39
|
+
/** Describes where in a flow definition a finding is located. */
|
|
40
|
+
export type FindingLocation = {
|
|
41
|
+
type: 'transition';
|
|
42
|
+
fromState: string;
|
|
43
|
+
toState: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'state';
|
|
46
|
+
state: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'data';
|
|
49
|
+
dataKey: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'flow';
|
|
52
|
+
};
|
|
53
|
+
/** A single analysis finding. */
|
|
54
|
+
export interface FindingEntry {
|
|
55
|
+
pluginId: string;
|
|
56
|
+
severity: string;
|
|
57
|
+
message: string;
|
|
58
|
+
location?: FindingLocation;
|
|
59
|
+
}
|
|
39
60
|
/** Plugin report — collects analysis findings. */
|
|
40
61
|
export declare class PluginReport {
|
|
41
62
|
private entries;
|
|
42
63
|
add(pluginId: string, severity: string, message: string): void;
|
|
43
64
|
warn(pluginId: string, message: string): void;
|
|
44
65
|
error(pluginId: string, message: string): void;
|
|
66
|
+
warnAt(pluginId: string, message: string, location: FindingLocation): void;
|
|
67
|
+
errorAt(pluginId: string, message: string, location: FindingLocation): void;
|
|
45
68
|
asText(): string;
|
|
46
|
-
findings():
|
|
47
|
-
pluginId: string;
|
|
48
|
-
severity: string;
|
|
49
|
-
message: string;
|
|
50
|
-
}[];
|
|
69
|
+
findings(): FindingEntry[];
|
|
51
70
|
}
|
package/dist/esm/api/types.js
CHANGED
|
@@ -10,10 +10,29 @@ export class PluginReport {
|
|
|
10
10
|
error(pluginId, message) {
|
|
11
11
|
this.add(pluginId, 'ERROR', message);
|
|
12
12
|
}
|
|
13
|
+
warnAt(pluginId, message, location) {
|
|
14
|
+
this.entries.push({ pluginId, severity: 'WARN', message, location });
|
|
15
|
+
}
|
|
16
|
+
errorAt(pluginId, message, location) {
|
|
17
|
+
this.entries.push({ pluginId, severity: 'ERROR', message, location });
|
|
18
|
+
}
|
|
13
19
|
asText() {
|
|
14
20
|
if (this.entries.length === 0)
|
|
15
21
|
return 'No findings.';
|
|
16
|
-
return this.entries.map(e =>
|
|
22
|
+
return this.entries.map(e => {
|
|
23
|
+
let text = `[${e.severity}] ${e.pluginId}: ${e.message}`;
|
|
24
|
+
if (e.location)
|
|
25
|
+
text += ` @ ${formatLocation(e.location)}`;
|
|
26
|
+
return text;
|
|
27
|
+
}).join('\n');
|
|
17
28
|
}
|
|
18
29
|
findings() { return [...this.entries]; }
|
|
19
30
|
}
|
|
31
|
+
function formatLocation(loc) {
|
|
32
|
+
switch (loc.type) {
|
|
33
|
+
case 'transition': return `transition(${loc.fromState} -> ${loc.toState})`;
|
|
34
|
+
case 'state': return `state(${loc.state})`;
|
|
35
|
+
case 'data': return `data(${loc.dataKey})`;
|
|
36
|
+
case 'flow': return 'flow';
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { PluginReport } from './api/types.js';
|
|
2
|
-
export type { PluginKind, PluginDescriptor, FlowPlugin, AnalysisPlugin, StorePlugin, EnginePlugin, RuntimeAdapterPlugin, GenerationPlugin, DocumentationPlugin as DocumentationPluginSPI, } from './api/types.js';
|
|
2
|
+
export type { FindingLocation, FindingEntry, PluginKind, PluginDescriptor, FlowPlugin, AnalysisPlugin, StorePlugin, EnginePlugin, RuntimeAdapterPlugin, GenerationPlugin, DocumentationPlugin as DocumentationPluginSPI, } from './api/types.js';
|
|
3
3
|
export { PluginRegistry } from './api/plugin-registry.js';
|
|
4
4
|
export { AuditStorePlugin } from './audit/audit-store-plugin.js';
|
|
5
5
|
export { AuditingFlowStore } from './audit/auditing-flow-store.js';
|
|
@@ -32,5 +32,5 @@ export { allDefaultPolicies } from './lint/default-flow-policies.js';
|
|
|
32
32
|
export type { FlowPolicy } from './lint/types.js';
|
|
33
33
|
export { ScenarioTestPlugin } from './testing/scenario-test-plugin.js';
|
|
34
34
|
export { ScenarioGenerationPlugin } from './testing/scenario-generation-plugin.js';
|
|
35
|
-
export type { FlowScenario, FlowTestPlan } from './testing/types.js';
|
|
35
|
+
export type { FlowScenario, FlowTestPlan, ScenarioKind } from './testing/types.js';
|
|
36
36
|
export { GuaranteedSubflowValidator } from './subflow/guaranteed-subflow-validator.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function warnTerminalWithOutgoing(def, report) {
|
|
2
2
|
for (const state of def.terminalStates) {
|
|
3
3
|
if (def.transitionsFrom(state).length > 0) {
|
|
4
|
-
report.
|
|
4
|
+
report.warnAt('policy/terminal-outgoing', `terminal state ${state} has outgoing transitions`, { type: 'state', state });
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
}
|
|
@@ -9,7 +9,7 @@ function warnTooManyExternals(def, report) {
|
|
|
9
9
|
for (const state of def.allStates()) {
|
|
10
10
|
const externals = def.transitionsFrom(state).filter(t => t.type === 'external');
|
|
11
11
|
if (externals.length > 3) {
|
|
12
|
-
report.
|
|
12
|
+
report.warnAt('policy/external-count', `state ${state} has ${externals.length} external transitions`, { type: 'state', state });
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -18,13 +18,13 @@ function warnDeadProducedData(def, report) {
|
|
|
18
18
|
return;
|
|
19
19
|
const dead = def.dataFlowGraph.deadData();
|
|
20
20
|
for (const key of dead) {
|
|
21
|
-
report.
|
|
21
|
+
report.warnAt('policy/dead-data', `produced but never consumed: ${key}`, { type: 'data', dataKey: key });
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
function warnOverwideProcessors(def, report) {
|
|
25
25
|
for (const t of def.transitions) {
|
|
26
26
|
if (t.processor && t.processor.produces.length > 3) {
|
|
27
|
-
report.
|
|
27
|
+
report.warnAt('policy/overwide-processor', `${t.processor.name} produces ${t.processor.produces.length} types; consider splitting it`, { type: 'transition', fromState: t.from, toState: t.to });
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -2,6 +2,7 @@ import type { FlowDefinition } from '@unlaxer/tramli';
|
|
|
2
2
|
import type { FlowTestPlan } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates BDD-style test scenarios from a flow definition.
|
|
5
|
+
* Covers happy paths, error transitions, guard rejections, and timeout expiry.
|
|
5
6
|
*/
|
|
6
7
|
export declare class ScenarioTestPlugin {
|
|
7
8
|
generate<S extends string>(definition: FlowDefinition<S>): FlowTestPlan;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generates BDD-style test scenarios from a flow definition.
|
|
3
|
+
* Covers happy paths, error transitions, guard rejections, and timeout expiry.
|
|
3
4
|
*/
|
|
4
5
|
export class ScenarioTestPlugin {
|
|
5
6
|
generate(definition) {
|
|
6
7
|
const scenarios = [];
|
|
8
|
+
// Happy path scenarios from transitions
|
|
7
9
|
for (const t of definition.transitions) {
|
|
8
10
|
const steps = [];
|
|
9
11
|
steps.push(`given flow in ${t.from}`);
|
|
@@ -17,7 +19,67 @@ export class ScenarioTestPlugin {
|
|
|
17
19
|
steps.push(`when branch ${t.branch.name} selects a route`);
|
|
18
20
|
}
|
|
19
21
|
steps.push(`then flow reaches ${t.to}`);
|
|
20
|
-
scenarios.push({ name: `${t.from}_to_${t.to}`, steps });
|
|
22
|
+
scenarios.push({ name: `${t.from}_to_${t.to}`, kind: 'happy', steps });
|
|
23
|
+
}
|
|
24
|
+
// Error path scenarios from errorTransitions
|
|
25
|
+
for (const [from, to] of definition.errorTransitions) {
|
|
26
|
+
scenarios.push({
|
|
27
|
+
name: `error_${from}_to_${to}`,
|
|
28
|
+
kind: 'error',
|
|
29
|
+
steps: [
|
|
30
|
+
`given flow in ${from}`,
|
|
31
|
+
`when processor throws an error`,
|
|
32
|
+
`then flow transitions to ${to} via on_error`,
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Exception route scenarios
|
|
37
|
+
if (definition.exceptionRoutes) {
|
|
38
|
+
for (const [from, routes] of definition.exceptionRoutes) {
|
|
39
|
+
for (const route of routes) {
|
|
40
|
+
const label = route.errorClass?.name ?? 'error';
|
|
41
|
+
scenarios.push({
|
|
42
|
+
name: `step_error_${from}_${label}_to_${route.target}`,
|
|
43
|
+
kind: 'error',
|
|
44
|
+
steps: [
|
|
45
|
+
`given flow in ${from}`,
|
|
46
|
+
`when error matching ${label} is thrown`,
|
|
47
|
+
`then flow transitions to ${route.target} via on_step_error`,
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Guard rejection scenarios
|
|
54
|
+
for (const t of definition.transitions) {
|
|
55
|
+
if (t.type === 'external' && t.guard) {
|
|
56
|
+
const errorTarget = definition.errorTransitions.get(t.from);
|
|
57
|
+
scenarios.push({
|
|
58
|
+
name: `guard_reject_${t.from}_${t.guard.name}`,
|
|
59
|
+
kind: 'guard_rejection',
|
|
60
|
+
steps: [
|
|
61
|
+
`given flow in ${t.from}`,
|
|
62
|
+
`when guard ${t.guard.name} rejects ${definition.maxGuardRetries} times`,
|
|
63
|
+
errorTarget
|
|
64
|
+
? `then flow transitions to ${errorTarget} via error`
|
|
65
|
+
: `then flow enters TERMINAL_ERROR`,
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Timeout scenarios
|
|
71
|
+
for (const t of definition.transitions) {
|
|
72
|
+
if (t.timeout != null) {
|
|
73
|
+
scenarios.push({
|
|
74
|
+
name: `timeout_${t.from}`,
|
|
75
|
+
kind: 'timeout',
|
|
76
|
+
steps: [
|
|
77
|
+
`given flow in ${t.from}`,
|
|
78
|
+
`when per-state timeout of ${t.timeout}ms expires`,
|
|
79
|
+
`then flow completes as EXPIRED`,
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
21
83
|
}
|
|
22
84
|
return { scenarios };
|
|
23
85
|
}
|
package/package.json
CHANGED