From 12b3d57808aa96738d198395e8eadf58e99274d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20Edman?= Date: Sun, 29 Dec 2024 09:38:55 +0100 Subject: [PATCH] accept options when deleting from storaga adapter --- CHANGELOG.md | 4 + docs/API.md | 3 +- example/processes/task.bpmn | 2 +- package.json | 7 +- test/features/memory-adapter-feature.js | 133 +++++++++ test/features/storage-adapter-feature.js | 326 +++++++---------------- types/index.d.ts | 2 +- types/interfaces.d.ts | 2 +- 8 files changed, 246 insertions(+), 233 deletions(-) create mode 100644 test/features/memory-adapter-feature.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0940c79..a8ae0f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## [0.15.1] - 2024-12-28 + +- accept options when deleting from storaga adapter + ## [0.15.0] - 2024-12-27 - add ability to pass custom resume engine request handler function diff --git a/docs/API.md b/docs/API.md index 3cd0afd..6f8971d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -351,12 +351,13 @@ Fetch entry by key. - `options`: optional object with options - `exclude`: optional list of fields to exclude -### `async delete(type, key)` +### `async delete(type, key[, options])` Delete entry by key. - `type`: string, storage type, `deployment`, `file`, or `state` - `key`: string, storage key +- `options`: optional object with options ### `async query(type, qs[, options])` diff --git a/example/processes/task.bpmn b/example/processes/task.bpmn index c01963e..ccbb9d4 100644 --- a/example/processes/task.bpmn +++ b/example/processes/task.bpmn @@ -1,6 +1,6 @@ - + Flow_1onlbrs diff --git a/package.json b/package.json index 64b3b22..aa5bec5 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bpmn-middleware", - "version": "0.15.0", + "version": "0.15.1", "description": "BPMN engine express middleware", "type": "module", "main": "./dist/main.cjs", @@ -43,7 +43,7 @@ }, "homepage": "https://github.com/zerodep/bpmn-middleware#readme", "devDependencies": { - "@onify/flow-extensions": "^8.3.0", + "@onify/flow-extensions": "^9.0.0", "@rollup/plugin-commonjs": "^28.0.0", "@types/bpmn-moddle": "^5.1.11", "@types/debug": "^4.1.12", @@ -72,10 +72,11 @@ "multer": "^1.4.5-lts.1" }, "peerDependencies": { - "bpmn-engine": ">=15", "bpmn-elements": "*", + "bpmn-engine": ">=15", "debug": "*", "express": ">=4", + "moddle-context-serializer": "*", "smqp": "*" }, "files": [ diff --git a/test/features/memory-adapter-feature.js b/test/features/memory-adapter-feature.js new file mode 100644 index 0000000..2a526fb --- /dev/null +++ b/test/features/memory-adapter-feature.js @@ -0,0 +1,133 @@ +import request from 'supertest'; +import { LRUCache } from 'lru-cache'; + +import { getAppWithExtensions, createDeployment, waitForProcess } from '../helpers/test-helpers.js'; +import { MemoryAdapter } from '../../src/index.js'; + +Feature('memory adapter', () => { + Scenario('built in memory adapter', () => { + let app1, app2, storage; + after(() => { + return Promise.all([ + request(app1).delete('/rest/internal/stop').expect(204), + request(app2).delete('/rest/internal/stop').expect(204), + ]); + }); + + Given('two parallel app instances with a shared adapter storage', () => { + storage = new LRUCache({ max: 100 }); + const adapter1 = new MemoryAdapter(storage); + const adapter2 = new MemoryAdapter(storage); + + app1 = getAppWithExtensions({ adapter: adapter1 }); + app2 = getAppWithExtensions({ adapter: adapter2 }); + }); + + And('a process with a user task with a non-interrupting bound timeout', () => { + return createDeployment( + app2, + 'memory-adapter', + ` + + + + + + PT10S + + + + ` + ); + }); + + let response, bp; + When('process is started', async () => { + response = await request(app1).post('/rest/process-definition/memory-adapter/start').expect(201); + + bp = response.body; + }); + + Then('process status is running timer', async () => { + response = await request(app2).get(`/rest/status/${bp.id}`); + + expect(response.statusCode, response.text).to.equal(200); + expect(response.body).to.have.property('state', 'running'); + expect(response.body).to.have.property('activityStatus', 'timer'); + expect(response.body).to.have.property('expireAt').that.is.ok; + }); + + Given('process run is stopped', () => { + return request(app1).delete(`/rest/internal/stop/${bp.id}`).expect(204); + }); + + When('process status is fetched', async () => { + response = await request(app2).get(`/rest/status/${bp.id}`); + }); + + Then('status is still running', () => { + expect(response.statusCode, response.text).to.equal(200); + expect(response.body).to.have.property('state', 'running'); + expect(response.body).to.have.property('activityStatus', 'timer'); + expect(response.body).to.have.property('expireAt').that.is.ok; + }); + + let end; + When('process user task is signaled', () => { + end = waitForProcess(app2, bp.id).end(); + return request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }).expect(200); + }); + + Then('run completes', () => { + return end; + }); + + And('first app also has the completed process', async () => { + response = await request(app1).get(`/rest/status/${bp.id}`); + + expect(response.statusCode, response.text).to.equal(200); + expect(response.body).to.have.property('state', 'idle'); + }); + + When('second app signals the completed process', async () => { + response = await request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }); + }); + + Then('bad request is returned with completed message', () => { + expect(response.statusCode, response.text).to.equal(400); + expect(response.body) + .to.have.property('message') + .that.match(/completed/i); + }); + + When('first app attempts to signal the completed process', async () => { + response = await request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }); + }); + + Then('bad request is returned with completed message', () => { + expect(response.statusCode, response.text).to.equal(400); + expect(response.body) + .to.have.property('message') + .that.match(/completed/i); + }); + + Given('the state is purged', () => { + storage.delete(`state:${bp.id}`); + }); + + When('first app attempts to signal the completed process', async () => { + response = await request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }); + }); + + Then('not found is returned', () => { + expect(response.statusCode, response.text).to.equal(404); + }); + + When('process is ran again', async () => { + response = await request(app1).post('/rest/process-definition/memory-adapter/start').expect(201); + + bp = response.body; + }); + }); +}); diff --git a/test/features/storage-adapter-feature.js b/test/features/storage-adapter-feature.js index decdc5e..ea58e0c 100644 --- a/test/features/storage-adapter-feature.js +++ b/test/features/storage-adapter-feature.js @@ -3,11 +3,12 @@ import * as ck from 'chronokinesis'; import { LRUCache } from 'lru-cache'; import FormData from 'form-data'; -import { getAppWithExtensions, createDeployment, waitForProcess, horizontallyScaled } from '../helpers/test-helpers.js'; +import { createDeployment, waitForProcess, horizontallyScaled } from '../helpers/test-helpers.js'; import { MemoryAdapter, STORAGE_TYPE_STATE, STORAGE_TYPE_FILE, STORAGE_TYPE_DEPLOYMENT, DEFAULT_IDLE_TIMER } from '../../src/index.js'; class StorageAdapter { - constructor() { + constructor({ storeSerialized } = {}) { + this.storeSerialized = storeSerialized; this[STORAGE_TYPE_STATE] = new Map(); this[STORAGE_TYPE_FILE] = new Map(); this[STORAGE_TYPE_DEPLOYMENT] = new Map(); @@ -21,7 +22,7 @@ class StorageAdapter { }) ); } - deleteByKey(/* type, key */) { + deleteByKey(/* type, key, options */) { throw new Error('not implemented'); } fetch(type, key /* options */) { @@ -40,7 +41,7 @@ class StorageAdapter { Feature('storage adapter', () => { after(ck.reset); - Scenario('storage adapter', () => { + Scenario('custom storage adapter', () => { let apps, adapter; after(() => { return apps.stop(); @@ -56,17 +57,17 @@ Feature('storage adapter', () => { apps.balance(), 'memory-adapter', ` - - - - - - PT10S - - - - ` + + + + + + PT10S + + + + ` ); }); @@ -171,17 +172,17 @@ Feature('storage adapter', () => { apps = horizontallyScaled(2, { adapter }); }); - And('a process with a user task with a non-interrupting bound timeout', () => { + And('a process with only a start event', () => { return createDeployment( apps.balance(), 'fast-process', ` - - - - - ` + + + + + ` ); }); @@ -218,18 +219,17 @@ Feature('storage adapter', () => { apps.balance(), 'multi-user-task', ` - - - - - - - - - - - ` + + + + + + + + + + + ` ); }); @@ -319,132 +319,6 @@ Feature('storage adapter', () => { }); }); - Scenario('built in memory adapter', () => { - let app1, app2, storage; - after(() => { - return Promise.all([ - request(app1).delete('/rest/internal/stop').expect(204), - request(app2).delete('/rest/internal/stop').expect(204), - ]); - }); - - Given('two parallel app instances with a shared adapter source', () => { - storage = new LRUCache({ max: 100 }); - const adapter1 = new MemoryAdapter(storage); - const adapter2 = new MemoryAdapter(storage); - - app1 = getAppWithExtensions({ adapter: adapter1 }); - app2 = getAppWithExtensions({ adapter: adapter2 }); - }); - - And('a process with a user task with a non-interrupting bound timeout', () => { - return createDeployment( - app2, - 'memory-adapter', - ` - - - - - - PT10S - - - - ` - ); - }); - - let response, bp; - When('process is started', async () => { - response = await request(app1).post('/rest/process-definition/memory-adapter/start').expect(201); - - bp = response.body; - }); - - Then('process status is running timer', async () => { - response = await request(app2).get(`/rest/status/${bp.id}`); - - expect(response.statusCode, response.text).to.equal(200); - expect(response.body).to.have.property('state', 'running'); - expect(response.body).to.have.property('activityStatus', 'timer'); - expect(response.body).to.have.property('expireAt').that.is.ok; - }); - - Given('process run is stopped', () => { - return request(app1).delete(`/rest/internal/stop/${bp.id}`).expect(204); - }); - - When('process status is fetched', async () => { - response = await request(app2).get(`/rest/status/${bp.id}`); - }); - - Then('status is still running', () => { - expect(response.statusCode, response.text).to.equal(200); - expect(response.body).to.have.property('state', 'running'); - expect(response.body).to.have.property('activityStatus', 'timer'); - expect(response.body).to.have.property('expireAt').that.is.ok; - }); - - let end; - When('process user task is signaled', () => { - end = waitForProcess(app2, bp.id).end(); - return request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }).expect(200); - }); - - Then('run completes', () => { - return end; - }); - - And('first app also has the completed process', async () => { - response = await request(app1).get(`/rest/status/${bp.id}`); - - expect(response.statusCode, response.text).to.equal(200); - expect(response.body).to.have.property('state', 'idle'); - }); - - When('second app signals the completed process', async () => { - response = await request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }); - }); - - Then('bad request is returned with completed message', () => { - expect(response.statusCode, response.text).to.equal(400); - expect(response.body) - .to.have.property('message') - .that.match(/completed/i); - }); - - When('first app attempts to signal the completed process', async () => { - response = await request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }); - }); - - Then('bad request is returned with completed message', () => { - expect(response.statusCode, response.text).to.equal(400); - expect(response.body) - .to.have.property('message') - .that.match(/completed/i); - }); - - Given('the state is purged', () => { - storage.delete(`state:${bp.id}`); - }); - - When('first app attempts to signal the completed process', async () => { - response = await request(app2).post(`/rest/signal/${bp.id}`).send({ id: 'task' }); - }); - - Then('not found is returned', () => { - expect(response.statusCode, response.text).to.equal(404); - }); - - When('process is ran again', async () => { - response = await request(app1).post('/rest/process-definition/memory-adapter/start').expect(201); - - bp = response.body; - }); - }); - Scenario('storage adapter throws on create deployment', () => { let apps, storage; after(() => { @@ -472,12 +346,12 @@ Feature('storage adapter', () => { apps.balance(), 'faulty-adapter', ` - - - - - ` + + + + + ` ); }); @@ -513,27 +387,27 @@ Feature('storage adapter', () => { apps.balance(), 'faulty-adapter', ` - - - - - - PT20S - - - - - PT30S - - - - - PT10S - - - - ` + + + + + + PT20S + + + + + PT30S + + + + + PT10S + + + + ` ); }); @@ -589,27 +463,27 @@ Feature('storage adapter', () => { apps.balance(), 'faulty-adapter', ` - - - - - - PT20S - - - - - PT30S - - - - - PT10S - - - - ` + + + + + + PT20S + + + + + PT30S + + + + + PT10S + + + + ` ); }); @@ -672,12 +546,12 @@ Feature('storage adapter', () => { apps.balance(), 'faulty-adapter', ` - - - - - ` + + + + + ` ); }); @@ -717,12 +591,12 @@ Feature('storage adapter', () => { form.append( `${deploymentName}.bpmn`, ` - - - - - `, + + + + + `, `${deploymentName}.bpmn` ); @@ -779,12 +653,12 @@ Feature('storage adapter', () => { form.append( `${deploymentName}.bpmn`, ` - - - - - `, + + + + + `, `${deploymentName}.bpmn` ); diff --git a/types/index.d.ts b/types/index.d.ts index 9ee077a..d8db76d 100755 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -69,7 +69,7 @@ declare module 'bpmn-middleware' { upsert(type: string | StorageType, key: string, value: T, options?: any): Promise; update(type: string | StorageType, key: string, value: T, options?: any): Promise; fetch(type: string | StorageType, key: string, options?: any): Promise; - delete(type: string | StorageType, key: string): Promise; + delete(type: string | StorageType, key: string, options?: any): Promise; query(type: string | StorageType, qs: StorageQuery, options?: any): Promise<{ records: T[]; [x: string]: any }>; } diff --git a/types/interfaces.d.ts b/types/interfaces.d.ts index 1ef7376..9c09362 100644 --- a/types/interfaces.d.ts +++ b/types/interfaces.d.ts @@ -65,7 +65,7 @@ export interface IStorageAdapter { upsert(type: string | StorageType, key: string, value: T, options?: any): Promise; update(type: string | StorageType, key: string, value: T, options?: any): Promise; fetch(type: string | StorageType, key: string, options?: any): Promise; - delete(type: string | StorageType, key: string): Promise; + delete(type: string | StorageType, key: string, options?: any): Promise; query(type: string | StorageType, qs: StorageQuery, options?: any): Promise<{ records: T[]; [x: string]: any }>; }