Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,8 @@ auth.json
secrets.json

# AI folders
ai-docs/
ai-docs/

# JMeter
jmeter/results/
!jmeter/results/.gitkeep
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,63 @@ dist/
| `npm test` | Run tests |


## 🧪 Load Testing (JMeter)

Asynchronous (`sync=false`) load test for the account-opening workflow is executed via `docker-compose.test.yml`. JMeter joins the existing `bbt-development` Docker network and does not modify the main `docker-compose.yml`.

### Structure

```
jmeter/
├── tests/
│ └── workflow-test.jmx
└── results/ # gitignored; reports are written here
```

### Running

```bash
# Default: 10 users, 10s ramp-up, 5 loops, base URL http://host.docker.internal:4201
docker compose -f docker-compose.test.yml up --abort-on-container-exit

# Custom base URL (e.g. a service name on the same network)
VNEXT_BASE_URL=http://amorphie-workflow:4201 \
docker compose -f docker-compose.test.yml up --abort-on-container-exit

# Custom load profile
VNEXT_BASE_URL=http://host.docker.internal:4201 \
JMETER_USERS=50 JMETER_RAMPUP=30 JMETER_LOOPS=10 \
docker compose -f docker-compose.test.yml up --abort-on-container-exit

# Tear down
docker compose -f docker-compose.test.yml down
```

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `VNEXT_BASE_URL` | `http://host.docker.internal:4201` | vNext workflow engine base URL |
| `JMETER_USERS` | `10` | Number of concurrent threads (users) |
| `JMETER_RAMPUP` | `10` | Ramp-up time in seconds |
| `JMETER_LOOPS` | `5` | Iterations per thread |

### Scenario

Single thread group, full happy path:

1. `POST /instances/start?sync=false` → extract `id` → poll state until `status=A` (max 5 polls, 1s backoff while `B`)
2. `PATCH /transitions/select-demand-deposit?sync=false` → poll until `status=A`
3. `PATCH /transitions/submit-account-details?sync=false` → poll until `status=A`
4. `PATCH /transitions/confirm-account-opening?sync=false` (final transition; response not asserted)

If the polled status returns `F` (or unexpected `C`) at any intermediate step, the remaining samplers in that iteration are skipped and the next loop starts.

### Results

- Raw JTL: `./jmeter/results/result.jtl`
- HTML report: `./jmeter/results/html-report/index.html`

## 🤝 Contributing

1. Fork the repository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"mapping": {
"location": "./src/FunctionValidatePoliciesMapping.csx",
"code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKLy8vIDxzdW1tYXJ5PgovLy8gVGFzayAxIG1hcHBpbmcgZm9yIG11bHRpLXRhc2sgZnVuY3Rpb24gdGVzdC4KLy8vIENhbGxzIHZhbGlkYXRlLWFjY291bnQtcG9saWNpZXMgSFRUUCB0YXNrIHdpdGggaW5zdGFuY2UgZGF0YS4KLy8vIDwvc3VtbWFyeT4KcHVibGljIGNsYXNzIEZ1bmN0aW9uVmFsaWRhdGVQb2xpY2llc01hcHBpbmcgOiBJTWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFdvcmtmbG93VGFzayB0YXNrLCBTY3JpcHRDb250ZXh0IGNvbnRleHQpCiAgICB7CiAgICAgICAgdmFyIGh0dHBUYXNrID0gdGFzayBhcyBIdHRwVGFzazs7OwogICAgICAgIGlmIChodHRwVGFzayA9PSBudWxsKQogICAgICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSgpKTsKCiAgICAgICAgdmFyIHVzZXJTZXNzaW9uID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YT8udXNlclNlc3Npb247CiAgICAgICAgdmFyIGFjY291bnRUeXBlID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YT8uYWNjb3VudFR5cGU7CgogICAgICAgIGh0dHBUYXNrLlNldEJvZHkobmV3CiAgICAgICAgewogICAgICAgICAgICB1c2VySWQgPSB1c2VyU2Vzc2lvbj8udXNlcklkLAogICAgICAgICAgICBhY2NvdW50VHlwZSA9IGFjY291bnRUeXBlID8/ICJkZW1hbmQtZGVwb3NpdCIsCiAgICAgICAgICAgIGN1cnJlbmN5ID0gY29udGV4dC5JbnN0YW5jZT8uRGF0YT8uY3VycmVuY3ksCiAgICAgICAgICAgIHJlcXVlc3RlZEF0ID0gRGF0ZVRpbWUuVXRjTm93CiAgICAgICAgfSk7CgogICAgICAgIGh0dHBUYXNrLlNldEhlYWRlcnMobmV3IERpY3Rpb25hcnk8c3RyaW5nLCBzdHJpbmc/PgogICAgICAgIHsKICAgICAgICAgICAgWyJDb250ZW50LVR5cGUiXSA9ICJhcHBsaWNhdGlvbi9qc29uIiwKICAgICAgICAgICAgWyJYLUluc3RhbmNlLUlkIl0gPSBjb250ZXh0Lkluc3RhbmNlPy5JZC5Ub1N0cmluZygpLAogICAgICAgICAgICBbIlgtUmVxdWVzdC1JZCJdID0gY29udGV4dC5IZWFkZXJzP1sieC1yZXF1ZXN0LWlkIl0gPz8gR3VpZC5OZXdHdWlkKCkuVG9TdHJpbmcoKQogICAgICAgIH0pOwoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSgpKTsKICAgIH0KCiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gT3V0cHV0SGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpCiAgICB7CiAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UKICAgICAgICB7CiAgICAgICAgICAgIEtleSA9ICJwb2xpY3ktY2hlY2stcmVzdWx0IiwKICAgICAgICAgICAgRGF0YSA9IGNvbnRleHQuQm9keQogICAgICAgIH0pOwogICAgfQp9Cg=="
"code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYzsKdXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKLy8vIDxzdW1tYXJ5PgovLy8gVGFzayAxIG1hcHBpbmcgZm9yIG11bHRpLXRhc2sgZnVuY3Rpb24gdGVzdC4KLy8vIENhbGxzIHZhbGlkYXRlLWFjY291bnQtcG9saWNpZXMgSFRUUCB0YXNrIHdpdGggaW5zdGFuY2UgZGF0YS4KLy8vIDwvc3VtbWFyeT4KcHVibGljIGNsYXNzIEZ1bmN0aW9uVmFsaWRhdGVQb2xpY2llc01hcHBpbmcgOiBJTWFwcGluZwp7CiAgICBwdWJsaWMgVGFzazxTY3JpcHRSZXNwb25zZT4gSW5wdXRIYW5kbGVyKFdvcmtmbG93VGFzayB0YXNrLCBTY3JpcHRDb250ZXh0IGNvbnRleHQpCiAgICB7CiAgICAgICAgdmFyIGh0dHBUYXNrID0gdGFzayBhcyBIdHRwVGFzazsKICAgICAgICBpZiAoaHR0cFRhc2sgPT0gbnVsbCkKICAgICAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7CgogICAgICAgIHZhciB1c2VyU2Vzc2lvbiA9IGNvbnRleHQuSW5zdGFuY2U/LkRhdGE/LnVzZXJTZXNzaW9uOwogICAgICAgIHZhciBhY2NvdW50VHlwZSA9IGNvbnRleHQuSW5zdGFuY2U/LkRhdGE/LmFjY291bnRUeXBlOwoKICAgICAgICBodHRwVGFzay5TZXRCb2R5KG5ldwogICAgICAgIHsKICAgICAgICAgICAgdXNlcklkID0gdXNlclNlc3Npb24/LnVzZXJJZCwKICAgICAgICAgICAgYWNjb3VudFR5cGUgPSBhY2NvdW50VHlwZSA/PyAiZGVtYW5kLWRlcG9zaXQiLAogICAgICAgICAgICBjdXJyZW5jeSA9IGNvbnRleHQuSW5zdGFuY2U/LkRhdGE/LmN1cnJlbmN5LAogICAgICAgICAgICByZXF1ZXN0ZWRBdCA9IERhdGVUaW1lLlV0Y05vdwogICAgICAgIH0pOwoKICAgICAgICBodHRwVGFzay5TZXRIZWFkZXJzKG5ldyBEaWN0aW9uYXJ5PHN0cmluZywgc3RyaW5nPz4KICAgICAgICB7CiAgICAgICAgICAgIFsiQ29udGVudC1UeXBlIl0gPSAiYXBwbGljYXRpb24vanNvbiIsCiAgICAgICAgICAgIFsiWC1JbnN0YW5jZS1JZCJdID0gY29udGV4dC5JbnN0YW5jZT8uSWQuVG9TdHJpbmcoKSwKICAgICAgICAgICAgWyJYLVJlcXVlc3QtSWQiXSA9IGNvbnRleHQuSGVhZGVycz9bIngtcmVxdWVzdC1pZCJdID8/IEd1aWQuTmV3R3VpZCgpLlRvU3RyaW5nKCkKICAgICAgICB9KTsKCiAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHJldHVybiBUYXNrLkZyb21SZXN1bHQobmV3IFNjcmlwdFJlc3BvbnNlCiAgICAgICAgewogICAgICAgICAgICBLZXkgPSAicG9saWN5LWNoZWNrLXJlc3VsdCIsCiAgICAgICAgICAgIERhdGEgPSBjb250ZXh0LkJvZHkKICAgICAgICB9KTsKICAgIH0KfQo="
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class FunctionValidatePoliciesMapping : IMapping
{
public Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
{
var httpTask = task as HttpTask;;;
var httpTask = task as HttpTask;
if (httpTask == null)
return Task.FromResult(new ScriptResponse());

Expand Down
2 changes: 1 addition & 1 deletion core/Schemas/account-opening/account-type-selection.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
]
}
},
"additionalProperties": false
"additionalProperties": true
}
}
}
49 changes: 49 additions & 0 deletions core/Schemas/account-opening/initiate-account-opening.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"key": "initiate-account-opening",
"version": "1.0.0",
"domain": "core",
"flow": "sys-schemas",
"flowVersion": "1.0.0",
"tags": [
"banking",
"account-opening",
"start-transition",
"input-schema"
],
"attributes": {
"type": "workflow",
"schema": {
"$id": "https://schemas.vnext.com/banking/initiate-account-opening.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Initiate Account Opening Schema",
"description": "Schema for the start transition payload of the account-opening workflow",
"type": "object",
"required": [
"session"
],
"properties": {
"session": {
"type": "string",
"title": "Session",
"description": "Unique session identifier for the account opening request",
"minLength": 1
},
"customer": {
"type": "object",
"title": "Customer",
"description": "Customer information for the account opening request",
"properties": {
"ownerUserId": {
"type": "string",
"title": "Owner User ID",
"description": "Identifier of the user who owns the account opening request",
"minLength": 1
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
}
}
18 changes: 18 additions & 0 deletions core/Tasks/account-opening/get-instance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"key": "get-instance",
"version": "1.0.0",
"domain": "core",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": [
"account-opening",
"get-instance"
],
"attributes": {
"type": "13",
"config": {
"domain": "core",
"flow": "account-opening"
}
}
}
19 changes: 19 additions & 0 deletions core/Tasks/account-opening/get-instances.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"key": "get-instances",
"version": "1.0.0",
"domain": "core",
"flow": "sys-tasks",
"flowVersion": "1.0.0",
"tags": [
"account-opening",
"get-instances",
"filter"
],
"attributes": {
"type": "15",
"config": {
"domain": "core",
"flow": "account-opening"
}
}
}
34 changes: 33 additions & 1 deletion core/Workflows/account-opening/account-opening-workflow.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"versionStrategy": "Minor",
"timer": {
"reset": "OnEntry",
"duration": "PT1M"
"duration": "PT15M"
}
},
"labels": [
Expand Down Expand Up @@ -84,6 +84,12 @@
"label": "Hesap Açılışını Başlat"
}
],
"schema": {
"key": "initiate-account-opening",
"version": "1.0.0",
"domain": "core",
"flow": "sys-schemas"
},
"onExecutionTasks": [
{
"order": 1,
Expand Down Expand Up @@ -192,6 +198,32 @@
"location": "./src/TriggerGetInstanceTaskMapping.csx",
"code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgU3lzdGVtLlRleHQuSnNvbjsKCnB1YmxpYyBjbGFzcyBUcmlnZ2VyR2V0SW5zdGFuY2VUYXNrTWFwcGluZyA6IElNYXBwaW5nCnsKICAgIHB1YmxpYyBUYXNrPFNjcmlwdFJlc3BvbnNlPiBJbnB1dEhhbmRsZXIoV29ya2Zsb3dUYXNrIHRhc2ssIFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICB2YXIgdHJpZ2dlclRhc2sgPSAodGFzayBhcyBHZXRJbnN0YW5jZURhdGFUYXNrKSE7CiAgICAgICAKICAgICAgICB0cmlnZ2VyVGFzay5TZXRJbnN0YW5jZShjb250ZXh0Lkluc3RhbmNlLktleSk7CiAgICAgICAgCiAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewoKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSgpCiAgICAgICAgewogICAgICAgICAgICBEYXRhID0gbmV3CiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIG9sZEluc3RhbmNlPWNvbnRleHQuQm9keSwKICAgICAgICAgICAgICAgIHN1Y2Nlc3MgPSB0cnVlCiAgICAgICAgICAgIH0KICAgICAgICB9KTsKICAgIH0KfQoKcHVibGljIGNsYXNzIFJlcXVlc3RNb2RlbAp7CiAgICBwdWJsaWMgc3RyaW5nIGtleSB7IGdldDsgc2V0OyB9CiAgICBwdWJsaWMgZHluYW1pYyAgYXR0cmlidXRlcyB7IGdldDsgc2V0OyB9Cn0="
}
},
{
"order": 2,
"task": {
"key": "get-instances",
"domain": "core",
"version": "1.0.0",
"flow": "sys-tasks"
},
"mapping": {
"location": "./src/GetInstancesByOldKeyMapping.csx",
"code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKLy8vIDxzdW1tYXJ5PgovLy8gRmlsdGVycyB3b3JrZmxvdyBpbnN0YW5jZXMgYnkgdGhlICJvbGRLZXkiIHZhbHVlIHNlbnQgaW4gdGhlIHRyYW5zaXRpb24gYm9keS4KLy8vIDwvc3VtbWFyeT4KcHVibGljIGNsYXNzIEdldEluc3RhbmNlc0J5T2xkS2V5TWFwcGluZyA6IElNYXBwaW5nCnsKICAgIHB1YmxpYyBUYXNrPFNjcmlwdFJlc3BvbnNlPiBJbnB1dEhhbmRsZXIoV29ya2Zsb3dUYXNrIHRhc2ssIFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICB2YXIgaW5zdGFuY2VzVGFzayA9ICh0YXNrIGFzIEdldEluc3RhbmNlc1Rhc2spITsKCiAgICAgICAgc3RyaW5nPyBvbGRLZXkgPSBjb250ZXh0LkJvZHk/Lm9sZEtleT8uVG9TdHJpbmcoKTsKCiAgICAgICAgaW5zdGFuY2VzVGFzay5TZXRGaWx0ZXIobmV3CiAgICAgICAgewogICAgICAgICAgICBrZXkgPSBuZXcgeyBlcSA9IG9sZEtleSB9CiAgICAgICAgfSk7CgogICAgICAgIHJldHVybiBUYXNrLkZyb21SZXN1bHQobmV3IFNjcmlwdFJlc3BvbnNlKCkpOwogICAgfQoKICAgIHB1YmxpYyBUYXNrPFNjcmlwdFJlc3BvbnNlPiBPdXRwdXRIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZQogICAgICAgIHsKICAgICAgICAgICAgS2V5ID0gImdldC1pbnN0YW5jZXMtYnktb2xkLWtleS1yZXN1bHQiLAogICAgICAgICAgICBEYXRhID0gbmV3CiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIGluc3RhbmNlcyA9IGNvbnRleHQuQm9keSwKICAgICAgICAgICAgICAgIGZldGNoZWRBdCA9IFN5c3RlbS5EYXRlVGltZS5VdGNOb3cKICAgICAgICAgICAgfQogICAgICAgIH0pOwogICAgfQp9Cg=="
}
},
{
"order": 3,
"task": {
"key": "get-instance",
"domain": "core",
"version": "1.0.0",
"flow": "sys-tasks"
},
"mapping": {
"location": "./src/GetInstanceByOldKeyMapping.csx",
"code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKLy8vIDxzdW1tYXJ5PgovLy8gUmV0cmlldmVzIGEgc2luZ2xlIHdvcmtmbG93IGluc3RhbmNlIGJ5IHRoZSAib2xkS2V5IiB2YWx1ZSBzZW50IGluIHRoZSB0cmFuc2l0aW9uIGJvZHkuCi8vLyA8L3N1bW1hcnk+CnB1YmxpYyBjbGFzcyBHZXRJbnN0YW5jZUJ5T2xkS2V5TWFwcGluZyA6IElNYXBwaW5nCnsKICAgIHB1YmxpYyBUYXNrPFNjcmlwdFJlc3BvbnNlPiBJbnB1dEhhbmRsZXIoV29ya2Zsb3dUYXNrIHRhc2ssIFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICB2YXIgaW5zdGFuY2VUYXNrID0gKHRhc2sgYXMgR2V0SW5zdGFuY2VEYXRhVGFzaykhOwoKICAgICAgICBzdHJpbmc/IG9sZEtleSA9IGNvbnRleHQuQm9keT8ub2xkS2V5Py5Ub1N0cmluZygpOwoKICAgICAgICBpbnN0YW5jZVRhc2suU2V0SW5zdGFuY2Uob2xkS2V5KTsKCiAgICAgICAgcmV0dXJuIFRhc2suRnJvbVJlc3VsdChuZXcgU2NyaXB0UmVzcG9uc2UoKSk7CiAgICB9CgogICAgcHVibGljIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHJldHVybiBUYXNrLkZyb21SZXN1bHQobmV3IFNjcmlwdFJlc3BvbnNlCiAgICAgICAgewogICAgICAgICAgICBLZXkgPSAiZ2V0LWluc3RhbmNlLWJ5LW9sZC1rZXktcmVzdWx0IiwKICAgICAgICAgICAgRGF0YSA9IG5ldwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICBpbnN0YW5jZSA9IGNvbnRleHQuQm9keSwKICAgICAgICAgICAgICAgIGZldGNoZWRBdCA9IFN5c3RlbS5EYXRlVGltZS5VdGNOb3cKICAgICAgICAgICAgfQogICAgICAgIH0pOwogICAgfQp9Cg=="
}
}
]
},
Expand Down
33 changes: 33 additions & 0 deletions core/Workflows/account-opening/src/GetInstanceByOldKeyMapping.csx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading.Tasks;
using BBT.Workflow.Scripting;
using BBT.Workflow.Definitions;

/// <summary>
/// Retrieves a single workflow instance by the "oldKey" value sent in the transition body.
/// </summary>
public class GetInstanceByOldKeyMapping : IMapping
{
public Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
{
var instanceTask = (task as GetInstanceDataTask)!;

string? oldKey = context.Body?.oldKey?.ToString();

instanceTask.SetInstance(oldKey);
Comment on lines +12 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mapping lacks proper validation for the input task and the oldKey value. If the task cast fails or oldKey is missing from the body, the script will either throw a NullReferenceException or perform an invalid lookup. It is recommended to add defensive checks.

        var instanceTask = task as GetInstanceDataTask;
        if (instanceTask == null)
        {
            return Task.FromResult(new ScriptResponse { Key = "error", Data = "Invalid task type" });
        }

        string? oldKey = context.Body?.oldKey?.ToString();
        if (string.IsNullOrEmpty(oldKey))
        {
            return Task.FromResult(new ScriptResponse { Key = "error", Data = "oldKey is required" });
        }

        instanceTask.SetInstance(oldKey);


return Task.FromResult(new ScriptResponse());
}

public Task<ScriptResponse> OutputHandler(ScriptContext context)
{
return Task.FromResult(new ScriptResponse
{
Key = "get-instance-by-old-key-result",
Data = new
{
instance = context.Body,
fetchedAt = System.DateTime.UtcNow
}
});
}
}
36 changes: 36 additions & 0 deletions core/Workflows/account-opening/src/GetInstancesByOldKeyMapping.csx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using BBT.Workflow.Scripting;
using BBT.Workflow.Definitions;

/// <summary>
/// Filters workflow instances by the "oldKey" value sent in the transition body.
/// </summary>
public class GetInstancesByOldKeyMapping : IMapping
{
public Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
{
var instancesTask = (task as GetInstancesTask)!;

string? oldKey = context.Body?.oldKey?.ToString();

instancesTask.SetFilter(new
{
key = new { eq = oldKey }
});

return Task.FromResult(new ScriptResponse());
}

public Task<ScriptResponse> OutputHandler(ScriptContext context)
{
return Task.FromResult(new ScriptResponse
{
Key = "get-instances-by-old-key-result",
Data = new
{
instances = context.Body,
fetchedAt = System.DateTime.UtcNow
}
});
}
}
30 changes: 30 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
networks:
bbt-development:
external: true

services:
jmeter:
image: alpine/jmeter:latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the latest tag for the JMeter image can lead to non-deterministic builds and potential breaking changes when the image is updated. It is recommended to pin a specific version (e.g., 5.6) to ensure reproducibility.

    image: alpine/jmeter:5.6

container_name: jmeter
networks:
Comment on lines +6 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Using latest JMeter image tag can introduce non-deterministic test behavior over time.

Please pin this to a specific JMeter version (for example alpine/jmeter:5.6.3) instead of latest to keep CI and local runs reproducible and avoid unexpected changes when the upstream image updates.

Suggested change
jmeter:
image: alpine/jmeter:latest
container_name: jmeter
networks:
jmeter:
# Pin JMeter image version to keep CI and local runs reproducible
image: alpine/jmeter:5.6.3
container_name: jmeter
networks:

- bbt-development
environment:
- VNEXT_BASE_URL=${VNEXT_BASE_URL:-http://host.docker.internal:4201}
- JMETER_USERS=${JMETER_USERS:-10}
- JMETER_RAMPUP=${JMETER_RAMPUP:-10}
- JMETER_LOOPS=${JMETER_LOOPS:-5}
volumes:
- ./jmeter/tests:/tests
- ./jmeter/results:/results
working_dir: /tests
extra_hosts:
- "host.docker.internal:host-gateway"
entrypoint: ["jmeter"]
command: >
-n -t /tests/workflow-test.jmx
-Jbase.url=${VNEXT_BASE_URL:-http://host.docker.internal:4201}
-Jusers=${JMETER_USERS:-10}
-Jrampup=${JMETER_RAMPUP:-10}
-Jloops=${JMETER_LOOPS:-5}
-l /results/result.jtl
-e -o /results/html-report
Loading
Loading