Skip to content

Commit cfa6ec7

Browse files
committed
Decrease hardcoded assumptions for test scenarios
1 parent 0acb9d3 commit cfa6ec7

File tree

2 files changed

+121
-87
lines changed

2 files changed

+121
-87
lines changed

packages/cody/src/lib/hooks/behaviour-test-files/features/step_definitions/__feature__Steps.ts__tmpl__

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { before, binding, given, then, when } from "cucumber-tsflow";
22
import {<%= whenEvent.propertyName %>} from "@app/shared/commands/<%= serviceNames.fileName %>/<%= whenEvent.fileName %>";
33
import { Event } from "@event-engine/messaging/event";
44
import { getConfiguredMessageBox } from "@server/infrastructure/configuredMessageBox";
5-
import { getConfiguredEventStore } from "@server/infrastructure/configuredEventStore";
5+
import { getConfiguredEventStore, PUBLIC_STREAM, WRITE_MODEL_STREAM } from "@server/infrastructure/configuredEventStore";
66
import {<%= givenEvent.propertyName %>} from "@app/shared/events/<%= serviceNames.fileName %>/<%= aggregate %>/<%= givenEvent.fileName %>";
77
import {<%= thenEvent.propertyName %>} from "@app/shared/events/<%= serviceNames.fileName %>/<%= aggregate %>/<%= thenEvent.fileName %>";
88
import expect from "expect";
9-
9+
import { setMessageMetadata } from "@event-engine/messaging/message";
10+
import { AggregateMeta } from "@event-engine/infrastructure/AggregateRepository";
1011

1112
@binding()
1213
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -20,6 +21,9 @@ class <%= feature %>Steps {
2021
const listener = (streamName: string, events: Event[]) => {
2122
this.events.push(...events);
2223
};
24+
25+
this.eventStore.createStream(WRITE_MODEL_STREAM);
26+
this.eventStore.createStream(PUBLIC_STREAM);
2327
this.eventStore.attachAppendToListener(listener);
2428
}
2529

@@ -29,23 +33,15 @@ class <%= feature %>Steps {
2933
<%- givenPayload %>
3034
};
3135

32-
const event = <%= givenEvent.propertyName %>(payload);
33-
34-
await this.messageBox.dispatch(event.name, event.payload, event.meta);
35-
}
36-
37-
/* multiple givens via iterator like above
38-
@given('Car Added To Fleet')
39-
public async givenCarAddedToFleet(): Promise<void> {
40-
const payload = {
41-
'vehicleId': '6a76bead-46ce-4651-bea0-d8a387b2e9d0',
42-
};
36+
let event = <%= givenEvent.propertyName %>(payload);
4337

44-
const event = carAddedToFleet(payload);
38+
event = setMessageMetadata(event, AggregateMeta.ID, '<%= expectedIdentifier %>');
39+
event = setMessageMetadata(event, AggregateMeta.TYPE, '<%= givenAggregateMetaType %>');
40+
event = setMessageMetadata(event, AggregateMeta.VERSION, 1);
4541

42+
await this.eventStore.appendTo(WRITE_MODEL_STREAM, [event]);
4643
await this.messageBox.dispatch(event.name, event.payload, event.meta);
4744
}
48-
*/
4945

5046
@when('<%= when %>')
5147
public async when<%= whenEvent.className %>(): Promise<void> {

packages/cody/src/lib/hooks/on-feature.ts

Lines changed: 110 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { CodyHook, Node, NodeType } from "@proophboard/cody-types";
2-
import { parseJsonMetadata } from "@proophboard/cody-utils";
3-
import { Context } from "./context";
4-
import { getOriginalNode } from "./utils/get-original-node";
5-
import { names} from "@event-engine/messaging/helpers";
6-
import { formatFiles, generateFiles } from "@nx/devkit";
7-
import { CodyResponseException, withErrorCheck } from "./utils/error-handling";
8-
import { detectService } from "./utils/detect-service";
1+
import {CodyHook, CodyResponseType, Node, NodeType} from "@proophboard/cody-types";
2+
import {getSingleTarget, isCodyError, parseJsonMetadata} from "@proophboard/cody-utils";
3+
import {Context} from "./context";
4+
import {getOriginalNode} from "./utils/get-original-node";
5+
import {names} from "@event-engine/messaging/helpers";
6+
import {formatFiles, generateFiles} from "@nx/devkit";
7+
import {CodyResponseException, withErrorCheck} from "./utils/error-handling";
8+
import {detectService} from "./utils/detect-service";
99
import {flushChanges} from "nx/src/generators/tree";
1010
import {listChangesForCodyResponse} from "./utils/fs-tree";
11+
import {getNodeFromSyncedNodes} from "@cody-engine/cody/hooks/utils/node-tree";
12+
import {findAggregateState} from "@cody-engine/cody/hooks/utils/aggregate/find-aggregate-state";
13+
import {getVoMetadata} from "@cody-engine/cody/hooks/utils/value-object/get-vo-metadata";
1114

1215
const modeKey = "mode";
1316
const modeValueTest = "test-scenario";
@@ -18,10 +21,16 @@ const thenKey = "then";
1821
export const onFeature: CodyHook<Context> = async (feature: Node, ctx: Context) => {
1922
try {
2023
feature = getOriginalNode(feature, ctx);
21-
const featureMeta : any = feature?.getMetadata() ? parseJsonMetadata<{service?: string}>(feature) : {};
24+
const featureMeta : any = feature?.getMetadata() ? parseJsonMetadata<{service?: string, mode?: string}>(feature) : {};
2225
const parentContainer = feature.getParent();
2326
const parentContainerMeta : any = parentContainer?.getMetadata() ? parseJsonMetadata<{service?: string}>(parentContainer) : {};
2427

28+
if (featureMeta[modeKey] != modeValueTest && parentContainerMeta[modeKey] != modeValueTest) {
29+
return {
30+
cody: "Feature code generation is not yet implemented",
31+
}
32+
}
33+
2534
// add all test nodes to a map with their ID as the key, for easy access
2635
const validTestNodes = [NodeType.command, NodeType.event];
2736
const testNodesMap = new Map<any, Node>();
@@ -31,67 +40,64 @@ export const onFeature: CodyHook<Context> = async (feature: Node, ctx: Context)
3140
}
3241
});
3342

34-
// check if either the feature (the test) or its bounded context (the test container) have their mode set to "test"
35-
if (featureMeta[modeKey] == modeValueTest || parentContainerMeta[modeKey] == modeValueTest) {
36-
37-
let whenCommand : Node | undefined;
38-
39-
// find "when" command node
40-
feature.getChildren().forEach(function(elem) {
41-
if (elem.getType() == NodeType.command) {
42-
whenCommand = elem;
43-
}
44-
});
45-
46-
if (whenCommand) {
47-
const givenNodes : Array<Node> = [];
48-
const thenNodes : Array<Node> = [];
49-
let currentNode : Node | undefined = whenCommand;
50-
51-
// everything before the "when" command node is seen as "given"
52-
while (currentNode) {
53-
currentNode = testNodesMap.get(currentNode.getSources().first()?.getId()) || undefined;
54-
55-
if (currentNode) {
56-
givenNodes.unshift(currentNode);
57-
}
58-
}
59-
60-
// everything after the "when" command is "then"
61-
currentNode = whenCommand;
62-
while (currentNode) {
63-
currentNode = testNodesMap.get(currentNode.getTargets().first()?.getId()) || undefined;
64-
65-
if (currentNode) {
66-
thenNodes.push(currentNode);
67-
}
68-
}
69-
70-
const changesForCodyResponse = await createTestFiles(feature.getName(), featureMeta, givenNodes, whenCommand, thenNodes, ctx);
71-
72-
// for logging:
73-
const loggedNodes: Array<string> = [];
74-
loggedNodes.push("GIVEN");
75-
givenNodes.forEach(function(node) {
76-
loggedNodes.push(node.getName());
77-
});
78-
loggedNodes.push("WHEN");
79-
loggedNodes.push(whenCommand.getName());
80-
loggedNodes.push("THEN");
81-
thenNodes.forEach(function(node) {
82-
//@ToDo extract and slice expectedIdentifier
83-
loggedNodes.push(node.getName());
84-
});
85-
86-
return {
87-
cody: `Running test called "${feature.getName().trim()}".\nThese are the nodes included in the test: ${loggedNodes.toString()}`,
88-
details: changesForCodyResponse
89-
}
43+
let whenCommand : Node | undefined;
44+
45+
// find "when" command node
46+
feature.getChildren().forEach(function(elem) {
47+
if (elem.getType() == NodeType.command) {
48+
whenCommand = elem;
49+
}
50+
});
51+
52+
if (!whenCommand) {
53+
54+
return {
55+
cody: "Feature code generation is not yet implemented",
56+
}
57+
}
58+
59+
const givenNodes : Array<Node> = [];
60+
const thenNodes : Array<Node> = [];
61+
let currentNode : Node | undefined = whenCommand;
62+
63+
// everything before the "when" command node is seen as "given"
64+
while (currentNode) {
65+
currentNode = testNodesMap.get(currentNode.getSources().first()?.getId()) || undefined;
66+
67+
if (currentNode) {
68+
givenNodes.unshift(currentNode);
69+
}
70+
}
71+
72+
// everything after the "when" command is "then"
73+
currentNode = whenCommand;
74+
while (currentNode) {
75+
currentNode = testNodesMap.get(currentNode.getTargets().first()?.getId()) || undefined;
76+
77+
if (currentNode) {
78+
thenNodes.push(currentNode);
9079
}
9180
}
9281

82+
const changesForCodyResponse = await createTestFiles(feature.getName(), featureMeta, givenNodes, whenCommand, thenNodes, ctx);
83+
84+
// for logging:
85+
const loggedNodes: Array<string> = [];
86+
loggedNodes.push("GIVEN");
87+
givenNodes.forEach(function(node) {
88+
loggedNodes.push(node.getName());
89+
});
90+
loggedNodes.push("WHEN");
91+
loggedNodes.push(whenCommand.getName());
92+
loggedNodes.push("THEN");
93+
thenNodes.forEach(function(node) {
94+
//@ToDo extract and slice expectedIdentifier
95+
loggedNodes.push(node.getName());
96+
});
97+
9398
return {
94-
cody: "Feature code generation is not yet implemented",
99+
cody: `Running test called "${feature.getName().trim()}".\nThese are the nodes included in the test: ${loggedNodes.toString()}`,
100+
details: changesForCodyResponse
95101
}
96102
} catch (e) {
97103
if(e instanceof CodyResponseException) {
@@ -106,7 +112,39 @@ async function createTestFiles(featureName: string, featureMeta: any, givenNodes
106112
// if using a service from another board (e.g. Fleet Management), make sure to set this up in the test feature's metadata!
107113
const service = withErrorCheck(detectService, [whenCommand, ctx]);
108114

109-
const aggregate = 'car';
115+
let aggregate: Node | undefined;
116+
for (const [, syncedNode] of ctx.syncedNodes) {
117+
if(syncedNode.getType() === NodeType.command && syncedNode.getName() === whenCommand.getName()
118+
&& syncedNode.getTags().contains('pb:connected')) {
119+
const aggregateObj = getSingleTarget(syncedNode, NodeType.aggregate);
120+
121+
if(!isCodyError(aggregateObj)) {
122+
aggregate = aggregateObj;
123+
break;
124+
}
125+
}
126+
}
127+
128+
if (!aggregate) {
129+
throw new CodyResponseException({
130+
cody: 'Could not find aggregate for test generation.',
131+
type: CodyResponseType.Error
132+
});
133+
}
134+
135+
const givenNode = givenNodes[0];
136+
const syncedAggregate = withErrorCheck(getNodeFromSyncedNodes, [aggregate, ctx.syncedNodes]);
137+
const aggregateState = withErrorCheck(findAggregateState, [syncedAggregate, ctx]);
138+
const aggregateStateMeta = withErrorCheck(getVoMetadata, [aggregateState, ctx]);
139+
const aggregateStateNames = names(aggregateState.getName());
140+
141+
const thenNode = thenNodes[0];
142+
const body = '{'+thenNode.getDescription().replaceAll('\'', '"')+'}';
143+
console.log('Json body: '+ body);
144+
const thenNodeDescriptionObject = JSON.parse(body);
145+
146+
const aggregateIdentifierProperty = aggregateStateMeta.identifier as keyof typeof thenNodeDescriptionObject;
147+
const givenAggregateMetaType = `${names(service).className}.${names(aggregate.getName()).className}`;
110148

111149
// TODO: currently only using the first "when" & "then" nodes
112150
const substitutions = {
@@ -117,16 +155,16 @@ async function createTestFiles(featureName: string, featureMeta: any, givenNodes
117155
"given": featureMeta[givenKey],
118156
"when": featureMeta[whenKey],
119157
"then": featureMeta[thenKey],
120-
"givenEvent": names(givenNodes[0].getName()),
158+
"givenEvent": names(givenNode.getName()),
159+
"givenAggregateMetaType": givenAggregateMetaType,
121160
"whenEvent": names(whenCommand.getName()),
122161
"thenEvent": names(thenNodes[0].getName()),
123-
"givenPayload": givenNodes[0].getDescription(),
162+
"givenPayload": givenNode.getDescription(),
124163
"whenPayload": whenCommand.getDescription(),
125164
"thenPayload": thenNodes[0].getDescription(),
126-
"aggregate": aggregate,
127-
"expectedIdentifier": "6a76bead-46ce-4651-bea0-d8a387b2e9d0" // TODO: read from "then" node payload (convert to json, read & remove "expectedIdentifier", convert back to string)
165+
"aggregate": names(aggregate.getName()).fileName,
166+
"expectedIdentifier": thenNodeDescriptionObject[aggregateIdentifierProperty]
128167
}
129-
// console.log(substitutions);
130168

131169
// generate test files
132170
const {tree} = ctx;

0 commit comments

Comments
 (0)