Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6940c70
added WIP onFeature hook
kaitorched Aug 17, 2023
c3b6aa1
some more WIP stuff for finding the relevant nodes to be tested in th…
kaitorched Sep 6, 2023
7621055
now properly fetching the test nodes
kaitorched Sep 26, 2023
630fefa
some preparations for the generated test file
kaitorched Sep 27, 2023
3acc8b5
Require cucumber/cucumber
bofalke Aug 1, 2023
51ef746
Add first Gherkin Feature and Step Definitions
bofalke Aug 1, 2023
5951fe5
Add Step definition for AddCarToFleet
bofalke Aug 2, 2023
ff642bd
WIP
bofalke Sep 6, 2023
80f3fa5
Extend cucumber tests to represent example
bofalke Sep 19, 2023
fb21a0e
Update test assertion
bofalke Sep 19, 2023
f3c88c5
Add rough outline of step definition template
bofalke Sep 27, 2023
576fe5a
added substitutions object
kaitorched Sep 27, 2023
f3d6ffb
Adjustments in Behaviour Test generation
bofalke Sep 29, 2023
17e6be2
generated test file name now has the __tmpl__ removed
kaitorched Oct 5, 2023
d1316a2
Adjust step definition template
bofalke Oct 5, 2023
42ed33a
fix formatting of json payload
kaitorched Oct 5, 2023
765078d
cucumber fix for Windows
kaitorched Oct 5, 2023
949211b
- automatic feature file generation
kaitorched Oct 5, 2023
d66257e
Use feature metadata for feature file definition to prevent duplicates
bofalke Oct 6, 2023
1158673
Skip command and event generation inside test-scenarios
bofalke Nov 24, 2023
0acb9d3
Fix contribution mode path
bofalke Nov 29, 2023
cfa6ec7
Decrease hardcoded assumptions for test scenarios
bofalke Nov 29, 2023
376f49c
Fix missing NodeType impor in on-event.ts
bofalke Mar 27, 2024
4b9e886
Add givenIdentifier for feature generation
bofalke Mar 28, 2024
6543668
Refactor to aggregate steps for future abstraction
bofalke Mar 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,871 changes: 1,580 additions & 291 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
"test": "npx nx run-many -t test -p be fe shared",
"test-be": "npx nx run be:test",
"test-fe": "npx nx run fe:test",
"test-shared": "npx nx run shared:test"
"test-shared": "npx nx run shared:test",
"test-scenarios": "npx nx run be:cucumber"
},
"private": false,
"devDependencies": {
"@babel/preset-react": "^7.14.5",
"@cucumber/cucumber": "^9.3.0",
"@nx/cypress": "16.0.3",
"@nx/devkit": "^16.1.1",
"@nx/esbuild": "16.0.3",
Expand Down Expand Up @@ -48,6 +50,7 @@
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"babel-jest": "^29.4.1",
"cucumber-tsflow": "^4.0.6",
"cypress": "^12.2.0",
"esbuild": "^0.17.17",
"eslint": "~8.15.0",
Expand All @@ -57,6 +60,7 @@
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"expect": "^29.7.0",
"jest": "^29.4.1",
"jest-environment-jsdom": "^29.4.1",
"jest-environment-node": "^29.4.1",
Expand Down
14 changes: 14 additions & 0 deletions packages/be/cucumber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// cucumber.js
let common = [
'features/**/*.feature', // Specify our feature files
'--require-module ts-node/register', // typescript cucumber
'--require-module tsconfig-paths/register', // typescript cucumber
'--require ./features/step_definitions/**/*.ts', // Load step definitions
'--publish-quiet' // Remove publish message from output
].join(' ');

process.env['NODE_ENV'] = "test";

module.exports = {
default: common
};
9 changes: 9 additions & 0 deletions packages/be/features/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"moduleResolution": "node",
"experimentalDecorators": true,
"types": ["node"]
},
"include": ["step_definitions/**/*.ts"]
}
2 changes: 1 addition & 1 deletion packages/be/nodemon.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"watch": ["src", "../shared"],
"watch": ["src", "features", "../shared"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts", "../shared/**/*.spec.ts"],
"exec": "npx ts-node -r tsconfig-paths/register src/main.ts"
Expand Down
3 changes: 3 additions & 0 deletions packages/be/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"scripts": {
"cucumber": "npx cucumber-js -p default"
},
"dependencies": {
"@app/shared": "file:be/packages/shared",
"@event-engine/infrastructure": "file:be/packages/infrastructure",
Expand Down
9 changes: 9 additions & 0 deletions packages/be/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
}
}
},
"cucumber": {
"executor": "nx:run-commands",
"defaultConfiguration": "development",
"options": {
"cwd": "packages/be",
"color": true,
"command": "npm run cucumber"
}
},
"serve": {
"executor": "nx:run-commands",
"defaultConfiguration": "development",
Expand Down
27 changes: 27 additions & 0 deletions packages/be/src/environments/environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {Environment} from "./environment.schema";

export const env: Environment = {
production: false,
mode: "prototype",
keycloak: {
baseUrl: 'http://localhost:8080',
realm: 'app'
},
postgres: {
host: 'localhost',
port: 5433,
database: 'test',
user: 'dev',
password: 'dev',
max: 200
},
eventStore: {
adapter: "memory"
},
documentStore: {
adapter: "memory"
},
authentication: {
disabled: true
}
};
7 changes: 6 additions & 1 deletion packages/be/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./features/tsconfig.json"
}
],
"compilerOptions": {
"esModuleInterop": true
"esModuleInterop": true,
"moduleResolution": "node",
"experimentalDecorators": true
}
}
3 changes: 2 additions & 1 deletion packages/cody/codyconfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Map} from "immutable";
import {onCommand} from "./src/lib/hooks/on-command";
import {onEvent} from "./src/lib/hooks/on-event";
import {onFeature} from "./src/lib/hooks/on-feature";
import {onAggregate} from "./src/lib/hooks/on-aggregate";
import {onDocument} from "./src/lib/hooks/on-document";
import {onUi} from "./src/lib/hooks/on-ui";
Expand Down Expand Up @@ -34,7 +35,7 @@ module.exports = {
onCommand,
onDocument,
onEvent,
// onFeature: onFeatureHook,
onFeature,
// onFreeText: onFreeTextHook,
// onExternalSystem: onExternalSystemHook,
// onIcon: onIconHook,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Feature: <%= featureNames.name %>
Scenario: <%= featureNames.name %>
Given <%= given %>
When <%= when %>
Then <%= then %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"given": {
"events": [{
"<%= givenEvent.propertyName %>": {
"payload": {
<%- givenPayload %>
},
"id": "<%= givenIdentifier %>",
"aggregate": "<%= givenAggregateMetaType %>"
}
}]
},
"when": {
"commands": [
{
"<%= whenEvent.propertyName %>": {
"payload": {
<%- whenPayload %>
}
}
}
]
},
"then": {
"events": [
{
"<%= thenEvent.propertyName %>": {
"payload": {
<%- thenPayload %>
},
"id": "<%= expectedIdentifier %>"
}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { before, binding, given, then, when } from "cucumber-tsflow";
import {<%= whenEvent.propertyName %>} from "@app/shared/commands/<%= serviceNames.fileName %>/<%= whenEvent.fileName %>";
import { Event } from "@event-engine/messaging/event";
import { getConfiguredMessageBox } from "@server/infrastructure/configuredMessageBox";
import { getConfiguredEventStore, PUBLIC_STREAM, WRITE_MODEL_STREAM } from "@server/infrastructure/configuredEventStore";
import {<%= givenEvent.propertyName %>} from "@app/shared/events/<%= serviceNames.fileName %>/<%= aggregateNames.fileName %>/<%= givenEvent.fileName %>";
import {<%= thenEvent.propertyName %>} from "@app/shared/events/<%= serviceNames.fileName %>/<%= aggregateNames.fileName %>/<%= thenEvent.fileName %>";
import expect from "expect";
import { setMessageMetadata } from "@event-engine/messaging/message";
import { AggregateMeta } from "@event-engine/infrastructure/AggregateRepository";
import { readFileSync } from 'fs';
import { join } from 'path';

type FeaturePayload = {
given: {
events: [{[key: string]: {
payload: { [key: string]: string | number },
id: string,
aggregate: string
}}]
},
when: {
commands: [{[key: string]: {
payload: { [key: string]: string|number }
}}]
},
then: {
events: [{[key: string]: {
payload: { [key: string]: string|number },
id: string
}}]
},
};

@binding()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class <%= feature %>Steps {
private messageBox = getConfiguredMessageBox();
private eventStore = getConfiguredEventStore();
private events: Event[] = [];
private payloads: FeaturePayload | undefined;

@before()
public setup(scenario: any): void {
const scenarioName = scenario.pickle?.name;
this.payloads = JSON.parse(readFileSync(join(__dirname,'..','payloads', scenarioName.replaceAll(' ', '')+'Feature.json'), 'utf-8'));
const listener = (streamName: string, events: Event[]) => {
this.events.push(...events);
};

this.eventStore.createStream(WRITE_MODEL_STREAM);
this.eventStore.createStream(PUBLIC_STREAM);
this.eventStore.attachAppendToListener(listener);
}

@given('<%= given %>')
public async given<%= givenEvent.className %>(): Promise<void> {
const eventInfo = this.payloads!.given.events[0].<%= givenEvent.propertyName %>;

// @ts-ignore
let event = <%= givenEvent.propertyName %>(eventInfo.payload);

event = setMessageMetadata(event, AggregateMeta.ID, eventInfo.id);
event = setMessageMetadata(event, AggregateMeta.TYPE, eventInfo.aggregate);
event = setMessageMetadata(event, AggregateMeta.VERSION, 1);

await this.eventStore.appendTo(WRITE_MODEL_STREAM, [event]);
await this.messageBox.dispatch(event.name, event.payload, event.meta);
}

@when('<%= when %>')
public async when<%= whenEvent.className %>(): Promise<void> {
const whenCommandInfo = this.payloads!.when.commands[0].<%= whenEvent.propertyName %>;

// @ts-ignore
const command = <%= whenEvent.propertyName %>(whenCommandInfo.payload);

await this.messageBox.dispatch(command.name, command.payload, command.meta);
}

@then('<%= then %>')
public async then<%= thenEvent.className %>(): Promise<void> {
const thenEventInfo = this.payloads!.then.events[0].<%= thenEvent.propertyName %>;

// @ts-ignore
const expectedEvent = <%= thenEvent.propertyName %>(thenEventInfo.payload);

const newestEventToCompare = this.events.
filter((event: Event) => {
return event.name === expectedEvent.name && event.meta.aggregateId === thenEventInfo.id;
}).sort((event1, event2) => {
return event2.createdAt.getTime() - event1.createdAt.getTime();
}).pop();

expect(newestEventToCompare).toBeDefined();
expect(newestEventToCompare?.payload).toEqual(thenEventInfo.payload);
}
}
14 changes: 13 additions & 1 deletion packages/cody/src/lib/hooks/on-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "@proophboard/cody-utils";
import {detectService} from "./utils/detect-service";
import {findAggregateState} from "./utils/aggregate/find-aggregate-state";
import {findParentByType} from "./utils/node-tree";
import {asyncWithErrorCheck, CodyResponseException, withErrorCheck} from "./utils/error-handling";
import {getVoMetadata} from "./utils/value-object/get-vo-metadata";
import {updateProophBoardInfo} from "./utils/prooph-board-info";
Expand Down Expand Up @@ -44,8 +45,19 @@ export interface CommandMeta {
}

export const onCommand: CodyHook<Context> = async (command: Node, ctx: Context) => {

try {
const feature = findParentByType(command, NodeType.feature);

if(feature) {
const featureMeta = parseJsonMetadata<{mode?: string}>(feature);

if(!isCodyError(featureMeta) && featureMeta.mode === 'test-scenario') {
return {
cody: `Inside test feature, skipping generation of command "${command.getName()}".`,
}
}
}

const cmdNames = names(command.getName());
const aggregate = getSingleTarget(command, NodeType.aggregate);
const service = withErrorCheck(detectService, [command, ctx]);
Expand Down
17 changes: 16 additions & 1 deletion packages/cody/src/lib/hooks/on-event.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {CodyHook, Node} from "@proophboard/cody-types";
import {CodyHook, Node, NodeType} from "@proophboard/cody-types";
import {Context} from "./context";
import {CodyResponseException, withErrorCheck} from "./utils/error-handling";
import {names} from "@event-engine/messaging/helpers";
import {isCodyError, parseJsonMetadata} from "@proophboard/cody-utils";
import {detectService} from "./utils/detect-service";
import {findParentByType} from "./utils/node-tree";
import {findAggregateState} from "./utils/aggregate/find-aggregate-state";
import {getVoMetadata} from "./utils/value-object/get-vo-metadata";
import {flushChanges} from "nx/src/generators/tree";
Expand All @@ -22,6 +24,19 @@ import {getOriginalEvent} from "@cody-engine/cody/hooks/utils/event/get-original

export const onEvent: CodyHook<Context> = async (event: Node, ctx: Context) => {
try {

const feature = findParentByType(event, NodeType.feature);

if(feature) {
const featureMeta = parseJsonMetadata<{mode?: string}>(feature);

if(!isCodyError(featureMeta) && featureMeta.mode === 'test-scenario') {
return {
cody: `Inside test feature, skipping generation of event "${event.getName()}".`,
}
}
}

event = getOriginalEvent(event, ctx);
const eventNames = names(event.getName());
const service = withErrorCheck(detectService, [event, ctx]);
Expand Down
Loading