@emartech/program-executor 3.11.1 → 3.12.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/.env.example +9 -1
- package/.github/CODEOWNERS +1 -1
- package/.github/dependabot.yml +0 -7
- package/.github/workflows/main.yaml +17 -5
- package/docker-compose.yml +11 -0
- package/eslint.config.js +1 -5
- package/jest.config.js +13 -0
- package/package.json +12 -16
- package/src/create-test-databases.js +84 -0
- package/src/execution-time-exceeded-error/{index.spec.js → index.test.js} +3 -3
- package/src/graphql/{schema.spec.js → schema.test.js} +53 -24
- package/src/ignorable-error/{index.spec.js → index.test.js} +5 -5
- package/src/index-pubsub-e2e.test.js +94 -0
- package/src/index-pubsub.js +78 -0
- package/src/index-pubsub.test.js +154 -0
- package/src/{index.spec.js → index.test.js} +42 -38
- package/src/job-data-handler/{index.spec.js → index.test.js} +15 -21
- package/src/program-executor-processor/{index.spec.js → index.test.js} +59 -48
- package/src/program-handler/{index.spec.js → index.test.js} +58 -34
- package/src/queue-manager/{index.spec.js → index.test.js} +16 -19
- package/src/queue-manager-pubsub/index.js +33 -0
- package/src/queue-manager-pubsub/index.test.js +44 -0
- package/src/repositories/{programs.spec.js → programs.test.js} +54 -37
- package/src/retryable-error/{index.spec.js → index.test.js} +5 -5
- package/src/runid-generator/{index.spec.js → index.test.js} +2 -2
- package/src/test-helper/get-test-db-config.js +29 -0
- package/src/testSetup.js +23 -0
- package/src/testTeardown.js +13 -0
- package/src/setup.spec.js +0 -50
package/.env.example
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
DEBUG=program-executor*
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
POSTGRES_USER=developer
|
|
4
|
+
POSTGRES_HOST=localhost
|
|
5
|
+
POSTGRES_PASSWORD=development_secret
|
|
6
|
+
POSTGRES_PORT=5435
|
|
7
|
+
POSTGRES_DATABASE=programexecutor
|
|
8
|
+
|
|
9
|
+
PUBSUB_EMULATOR_HOST=0.0.0.0:8085
|
|
10
|
+
PUBSUB_PROJECT_ID=some-project-id
|
|
11
|
+
PUBSUB_TIMEOUT=1000
|
package/.github/CODEOWNERS
CHANGED
package/.github/dependabot.yml
CHANGED
|
@@ -19,13 +19,6 @@ updates:
|
|
|
19
19
|
interval: "weekly"
|
|
20
20
|
day: "monday"
|
|
21
21
|
time: "09:00"
|
|
22
|
-
# Add assignees
|
|
23
|
-
reviewers:
|
|
24
|
-
- "IvanFroehlich"
|
|
25
|
-
- "MauroGreco"
|
|
26
|
-
- "oliverweisenburger"
|
|
27
|
-
- "dimirovn"
|
|
28
|
-
- "ianhelmrich"
|
|
29
22
|
commit-message:
|
|
30
23
|
# Prefix all commit messages with "npm: "
|
|
31
24
|
prefix: "[dependabot]npm"
|
|
@@ -11,8 +11,6 @@ env:
|
|
|
11
11
|
jobs:
|
|
12
12
|
test:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
|
-
container: node:18-alpine3.16
|
|
15
|
-
|
|
16
14
|
services:
|
|
17
15
|
postgres:
|
|
18
16
|
image: postgres:14-alpine
|
|
@@ -29,12 +27,19 @@ jobs:
|
|
|
29
27
|
- 5432:5432
|
|
30
28
|
|
|
31
29
|
steps:
|
|
30
|
+
- name: 'Install Cloud SDK'
|
|
31
|
+
uses: google-github-actions/setup-gcloud@v2.1.4
|
|
32
|
+
with:
|
|
33
|
+
install_components: 'beta,pubsub-emulator'
|
|
34
|
+
- name: 'start pubsub emulator'
|
|
35
|
+
run: |
|
|
36
|
+
gcloud beta emulators pubsub start --project=some-project-id --host-port=0.0.0.0:8085 &
|
|
32
37
|
- name: Check out repository
|
|
33
38
|
uses: actions/checkout@v4
|
|
34
|
-
- name: Use
|
|
39
|
+
- name: Use Node 20.18.0 version
|
|
35
40
|
uses: actions/setup-node@v3
|
|
36
41
|
with:
|
|
37
|
-
node-version: "
|
|
42
|
+
node-version: "20.18.0"
|
|
38
43
|
- name: Set NPM token
|
|
39
44
|
run: npm config set '//registry.npmjs.org/:_authToken' "${{ env.NPM_TOKEN }}"
|
|
40
45
|
- name: npm dependencies
|
|
@@ -42,7 +47,14 @@ jobs:
|
|
|
42
47
|
- name: npm test
|
|
43
48
|
run: npm test
|
|
44
49
|
env:
|
|
45
|
-
|
|
50
|
+
POSTGRES_USER: developer
|
|
51
|
+
POSTGRES_HOST: localhost
|
|
52
|
+
POSTGRES_PASSWORD: development_secret
|
|
53
|
+
POSTGRES_PORT: 5432
|
|
54
|
+
POSTGRES_DATABASE: programexecutor
|
|
55
|
+
PUBSUB_EMULATOR_HOST: 0.0.0.0:8085
|
|
56
|
+
PUBSUB_PROJECT_ID: some-project-id
|
|
57
|
+
PUBSUB_TIMEOUT: 2000
|
|
46
58
|
|
|
47
59
|
deploy:
|
|
48
60
|
name: deploy
|
package/docker-compose.yml
CHANGED
|
@@ -7,3 +7,14 @@ services:
|
|
|
7
7
|
- POSTGRES_DB=programexecutor
|
|
8
8
|
ports:
|
|
9
9
|
- 5435:5432
|
|
10
|
+
pub-sub-emulator:
|
|
11
|
+
image: google/cloud-sdk:526.0.0-emulators
|
|
12
|
+
command: ["gcloud", "beta", "emulators", "pubsub", "start", "--host-port=0.0.0.0:8085", "--project=some-project-id"]
|
|
13
|
+
ports:
|
|
14
|
+
- "8085:8085"
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ["CMD", "curl", "-f", "http://localhost:8085/v1/projects/test/schemas"]
|
|
17
|
+
interval: 1m30s
|
|
18
|
+
timeout: 10s
|
|
19
|
+
retries: 3
|
|
20
|
+
start_period: 40s
|
package/eslint.config.js
CHANGED
|
@@ -14,14 +14,10 @@ const compat = new FlatCompat({
|
|
|
14
14
|
module.exports = [
|
|
15
15
|
...compat.extends('emarsys'),
|
|
16
16
|
{
|
|
17
|
-
plugins: {
|
|
18
|
-
mocha
|
|
19
|
-
},
|
|
20
|
-
|
|
21
17
|
languageOptions: {
|
|
22
18
|
globals: {
|
|
23
19
|
...globals.node,
|
|
24
|
-
...globals.
|
|
20
|
+
...globals.jest,
|
|
25
21
|
inject: true,
|
|
26
22
|
onmessage: true,
|
|
27
23
|
expect: true
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
rootDir: './',
|
|
6
|
+
testPathIgnorePatterns: ['/node_modules/'],
|
|
7
|
+
testRegex: '\\.(test|e2e)\\.js$',
|
|
8
|
+
clearMocks: true,
|
|
9
|
+
collectCoverage: false,
|
|
10
|
+
coverageReporters: ['json', 'html', 'text', 'lcov'],
|
|
11
|
+
globalSetup: "./src/testSetup.js",
|
|
12
|
+
globalTeardown: './src/testTeardown.js',
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
"description": "",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"test": "
|
|
6
|
+
"test": "npm run create-test-dbs && jest",
|
|
7
|
+
"jest-watch": "jest --watch",
|
|
8
|
+
"create-test-dbs": "NODE_ENV=test node ./src/create-test-databases.js",
|
|
7
9
|
"code-style": "eslint '**/*.js' --ignore-pattern node_modules/",
|
|
8
10
|
"semantic-release": "semantic-release --branches main"
|
|
9
11
|
},
|
|
@@ -17,31 +19,25 @@
|
|
|
17
19
|
"author": "team-shopify@emarsys.com",
|
|
18
20
|
"license": "ISC",
|
|
19
21
|
"dependencies": {
|
|
20
|
-
"@emartech/rabbitmq-client": "5.9.0",
|
|
21
22
|
"@emartech/json-logger": "3.4.0",
|
|
23
|
+
"@emartech/pubsub-client-js": "^1.2.5",
|
|
24
|
+
"@emartech/rabbitmq-client": "5.9.1",
|
|
22
25
|
"camelcase-keys": "^6.2.2",
|
|
23
|
-
"graphql": "^16.
|
|
26
|
+
"graphql": "^16.11.0"
|
|
24
27
|
},
|
|
25
28
|
"engines": {
|
|
26
29
|
"node": "20.18.0"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"chai-string": "1.5.0",
|
|
32
|
-
"chai-subset": "1.6.0",
|
|
33
|
-
"dotenv": "^16.4.7",
|
|
34
|
-
"eslint": "^9.21.0",
|
|
32
|
+
"dotenv": "^17.2.1",
|
|
33
|
+
"eslint": "^9.34.0",
|
|
35
34
|
"eslint-config-emarsys": "5.1.0",
|
|
36
|
-
"eslint-plugin-mocha": "^10.5.0",
|
|
37
35
|
"eslint-plugin-no-only-tests": "^3.3.0",
|
|
38
36
|
"eslint-plugin-security": "^3.0.1",
|
|
37
|
+
"jest": "29.0.0",
|
|
39
38
|
"knex": "^3.1.0",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"semantic-release": "^24.2.0",
|
|
43
|
-
"sinon": "19.0.2",
|
|
44
|
-
"sinon-chai": "^3.7.0"
|
|
39
|
+
"pg": "^8.16.3",
|
|
40
|
+
"semantic-release": "^24.2.0"
|
|
45
41
|
},
|
|
46
|
-
"version": "3.
|
|
42
|
+
"version": "3.12.0"
|
|
47
43
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('dotenv').config({ silent: true });
|
|
4
|
+
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const { db } = require('./test-helper/get-test-db-config');
|
|
8
|
+
|
|
9
|
+
const { Client } = require('pg');
|
|
10
|
+
|
|
11
|
+
const cpuCount = os.cpus() ? os.cpus().length : 1;
|
|
12
|
+
|
|
13
|
+
const defaultDbName = db.connection.database;
|
|
14
|
+
|
|
15
|
+
const getConnection = function (databaseName) {
|
|
16
|
+
return {
|
|
17
|
+
...db.connection,
|
|
18
|
+
database: databaseName
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const recreateTestDbs = async () => {
|
|
23
|
+
const client = new Client(getConnection('template1'));
|
|
24
|
+
|
|
25
|
+
await client.connect();
|
|
26
|
+
|
|
27
|
+
for (let index = 0; index <= cpuCount; index++) {
|
|
28
|
+
console.log(`Creating test database ${index}`);
|
|
29
|
+
try {
|
|
30
|
+
const pgUser = process.env.POSTGRES_USER || 'developer';
|
|
31
|
+
await client.query(`DROP DATABASE IF EXISTS ${defaultDbName}_${index}`);
|
|
32
|
+
await client.query(`CREATE DATABASE ${defaultDbName}_${index} TEMPLATE ${defaultDbName}`);
|
|
33
|
+
await client.query(`ALTER DATABASE ${defaultDbName}_${index} OWNER TO ${pgUser}`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.log(error.message);
|
|
36
|
+
await client.end();
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await client.end();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getLatestMigrationId = async (databaseName) => {
|
|
44
|
+
const client = new Client(getConnection(databaseName));
|
|
45
|
+
await client.connect();
|
|
46
|
+
|
|
47
|
+
let result;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
result = await client.query('SELECT MAX(id) FROM knex_migrations');
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.log(`Migrations not found in ${databaseName ? databaseName : defaultDbName}...`);
|
|
53
|
+
await client.end();
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await client.end();
|
|
58
|
+
return result.rows[0].max;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const isRecreationNeeded = async () => {
|
|
62
|
+
try {
|
|
63
|
+
const [mainMigrationId, secondaryMigrationId] = await Promise.all([
|
|
64
|
+
getLatestMigrationId(defaultDbName),
|
|
65
|
+
getLatestMigrationId(`${defaultDbName}_${cpuCount}`)
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
return mainMigrationId !== secondaryMigrationId;
|
|
69
|
+
// eslint-disable-next-line no-unused-vars
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
(async () => {
|
|
76
|
+
const recreate = await isRecreationNeeded();
|
|
77
|
+
|
|
78
|
+
if (recreate) {
|
|
79
|
+
await recreateTestDbs();
|
|
80
|
+
} else {
|
|
81
|
+
console.log('Everything is fine, using previously generated dbs...');
|
|
82
|
+
}
|
|
83
|
+
process.exit(0);
|
|
84
|
+
})();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const ExecutionTimeExceededError = require('
|
|
3
|
+
const ExecutionTimeExceededError = require('.');
|
|
4
4
|
|
|
5
5
|
describe('ExecutionTimeExceededError', () => {
|
|
6
6
|
it('should have a executionTimeExceeded property set to true', () => {
|
|
7
7
|
try {
|
|
8
8
|
throw new ExecutionTimeExceededError();
|
|
9
9
|
} catch (error) {
|
|
10
|
-
expect(error.executionTimeExceeded).
|
|
10
|
+
expect(error.executionTimeExceeded).toBe(true);
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ describe('ExecutionTimeExceededError', () => {
|
|
|
15
15
|
try {
|
|
16
16
|
throw new ExecutionTimeExceededError('Something bad happened!');
|
|
17
17
|
} catch (error) {
|
|
18
|
-
expect(error.message).
|
|
18
|
+
expect(error.message).toEqual('Something bad happened!');
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
});
|
|
@@ -3,8 +3,28 @@
|
|
|
3
3
|
const { graphql } = require('graphql');
|
|
4
4
|
const ProgramsRepository = require('../repositories/programs');
|
|
5
5
|
const schema = require('./schema');
|
|
6
|
+
const knex = require('knex');
|
|
7
|
+
const DbCleaner = require('../test-helper/db-cleaner');
|
|
8
|
+
const { db } = require('../test-helper/get-test-db-config');
|
|
6
9
|
|
|
7
10
|
describe('schema', function () {
|
|
11
|
+
|
|
12
|
+
let dbConnection;
|
|
13
|
+
|
|
14
|
+
beforeAll(async function () {
|
|
15
|
+
dbConnection = knex({
|
|
16
|
+
client: 'pg',
|
|
17
|
+
connection: db.connection.connString
|
|
18
|
+
});
|
|
19
|
+
await DbCleaner.create(dbConnection).tearDown();
|
|
20
|
+
ProgramsRepository.create(dbConnection, 'programs');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(async function () {
|
|
24
|
+
await DbCleaner.create(dbConnection).tearDown();
|
|
25
|
+
dbConnection.destroy();
|
|
26
|
+
});
|
|
27
|
+
|
|
8
28
|
describe('with empty database', () => {
|
|
9
29
|
it('returns with empty array when table does not exist (error code 42P01)', async function () {
|
|
10
30
|
const query = `
|
|
@@ -14,8 +34,9 @@ describe('schema', function () {
|
|
|
14
34
|
}
|
|
15
35
|
}`;
|
|
16
36
|
|
|
17
|
-
const { data } = await graphql({ schema, source: query, contextValue: { knex:
|
|
18
|
-
|
|
37
|
+
const { data } = await graphql({ schema, source: query, contextValue: { knex: dbConnection,
|
|
38
|
+
tableName: 'programs' } });
|
|
39
|
+
expect(data).toEqual({ programs: [] });
|
|
19
40
|
});
|
|
20
41
|
});
|
|
21
42
|
|
|
@@ -48,8 +69,11 @@ describe('schema', function () {
|
|
|
48
69
|
}
|
|
49
70
|
];
|
|
50
71
|
|
|
72
|
+
|
|
73
|
+
|
|
51
74
|
beforeEach(async function () {
|
|
52
|
-
|
|
75
|
+
await DbCleaner.create(dbConnection).tearDown();
|
|
76
|
+
const programsRepository = ProgramsRepository.create(dbConnection, 'programs');
|
|
53
77
|
for (const program of programs) {
|
|
54
78
|
await programsRepository.save(program);
|
|
55
79
|
}
|
|
@@ -73,33 +97,34 @@ describe('schema', function () {
|
|
|
73
97
|
}
|
|
74
98
|
}`;
|
|
75
99
|
|
|
76
|
-
const { data } = await graphql({ schema, source: query, contextValue: { knex:
|
|
100
|
+
const { data } = await graphql({ schema, source: query, contextValue: { knex: dbConnection,
|
|
101
|
+
tableName: 'programs' } });
|
|
77
102
|
|
|
78
|
-
expect(data.programs[0]).
|
|
103
|
+
expect(data.programs[0]).toEqual(expect.objectContaining({
|
|
79
104
|
...programs[0],
|
|
80
105
|
jobData: JSON.stringify({}),
|
|
81
106
|
programData: JSON.stringify(programs[0].programData)
|
|
82
|
-
});
|
|
83
|
-
expect(data.programs[0].createdAt).not.
|
|
84
|
-
expect(data.programs[0].updatedAt).not.
|
|
107
|
+
}));
|
|
108
|
+
expect(data.programs[0].createdAt).not.toBe(undefined);
|
|
109
|
+
expect(data.programs[0].updatedAt).not.toBe(undefined);
|
|
85
110
|
|
|
86
|
-
expect(data.programs[1]).
|
|
111
|
+
expect(data.programs[1]).toEqual(expect.objectContaining({
|
|
87
112
|
...programs[1],
|
|
88
113
|
jobData: JSON.stringify(programs[1].jobData),
|
|
89
114
|
programData: JSON.stringify(programs[1].programData),
|
|
90
115
|
erroredAt: programs[1].erroredAt.getTime().toString()
|
|
91
|
-
});
|
|
92
|
-
expect(data.programs[1].createdAt).not.
|
|
93
|
-
expect(data.programs[1].updatedAt).not.
|
|
116
|
+
}));
|
|
117
|
+
expect(data.programs[1].createdAt).not.toBe(undefined);
|
|
118
|
+
expect(data.programs[1].updatedAt).not.toBe(undefined);
|
|
94
119
|
|
|
95
|
-
expect(data.programs[2]).
|
|
120
|
+
expect(data.programs[2]).toEqual(expect.objectContaining({
|
|
96
121
|
...programs[2],
|
|
97
122
|
jobData: JSON.stringify(programs[2].jobData),
|
|
98
123
|
programData: JSON.stringify(programs[2].programData),
|
|
99
124
|
finishedAt: programs[2].finishedAt.getTime().toString()
|
|
100
|
-
});
|
|
101
|
-
expect(data.programs[2].createdAt).not.
|
|
102
|
-
expect(data.programs[2].updatedAt).not.
|
|
125
|
+
}));
|
|
126
|
+
expect(data.programs[2].createdAt).not.toBe(undefined);
|
|
127
|
+
expect(data.programs[2].updatedAt).not.toBe(undefined);
|
|
103
128
|
});
|
|
104
129
|
|
|
105
130
|
describe('accepts orderBy input for `id` field', () => {
|
|
@@ -113,11 +138,12 @@ describe('schema', function () {
|
|
|
113
138
|
const { data } = await graphql({
|
|
114
139
|
schema,
|
|
115
140
|
source: query,
|
|
116
|
-
contextValue: { knex:
|
|
141
|
+
contextValue: { knex: dbConnection,
|
|
142
|
+
tableName: 'programs' }
|
|
117
143
|
});
|
|
118
144
|
const ids = data.programs.map((program) => program.id);
|
|
119
145
|
|
|
120
|
-
expect(ids).
|
|
146
|
+
expect(ids).toEqual(['1', '2', '3']);
|
|
121
147
|
});
|
|
122
148
|
|
|
123
149
|
it('supports ordering by id DESC', async function () {
|
|
@@ -130,11 +156,12 @@ describe('schema', function () {
|
|
|
130
156
|
const { data } = await graphql({
|
|
131
157
|
schema,
|
|
132
158
|
source: query,
|
|
133
|
-
contextValue: { knex:
|
|
159
|
+
contextValue: { knex: dbConnection,
|
|
160
|
+
tableName: 'programs' }
|
|
134
161
|
});
|
|
135
162
|
const ids = data.programs.map((program) => program.id);
|
|
136
163
|
|
|
137
|
-
expect(ids).
|
|
164
|
+
expect(ids).toEqual(['3', '2', '1']);
|
|
138
165
|
});
|
|
139
166
|
});
|
|
140
167
|
|
|
@@ -149,11 +176,12 @@ describe('schema', function () {
|
|
|
149
176
|
const { data } = await graphql({
|
|
150
177
|
schema,
|
|
151
178
|
source: query,
|
|
152
|
-
contextValue: { knex:
|
|
179
|
+
contextValue: { knex: dbConnection,
|
|
180
|
+
tableName: 'programs' }
|
|
153
181
|
});
|
|
154
182
|
const ids = data.programs.map((program) => program.id);
|
|
155
183
|
|
|
156
|
-
expect(ids).
|
|
184
|
+
expect(ids).toEqual(['1']);
|
|
157
185
|
});
|
|
158
186
|
|
|
159
187
|
it('supports filtering for programs with step retry count greater than a given input', async function () {
|
|
@@ -166,11 +194,12 @@ describe('schema', function () {
|
|
|
166
194
|
const { data } = await graphql({
|
|
167
195
|
schema,
|
|
168
196
|
source: query,
|
|
169
|
-
contextValue: { knex:
|
|
197
|
+
contextValue: { knex: dbConnection,
|
|
198
|
+
tableName: 'programs' }
|
|
170
199
|
});
|
|
171
200
|
const ids = data.programs.map((program) => program.id);
|
|
172
201
|
|
|
173
|
-
expect(ids).
|
|
202
|
+
expect(ids).toEqual(['3']);
|
|
174
203
|
});
|
|
175
204
|
});
|
|
176
205
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const IgnorableError = require('
|
|
3
|
+
const IgnorableError = require('.');
|
|
4
4
|
|
|
5
5
|
describe('IgnorableError', () => {
|
|
6
6
|
it('should have a retryable property set to true', () => {
|
|
7
7
|
try {
|
|
8
8
|
throw new IgnorableError();
|
|
9
9
|
} catch (error) {
|
|
10
|
-
expect(error.ignorable).
|
|
10
|
+
expect(error.ignorable).toBe(true);
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
13
|
|
|
@@ -15,8 +15,8 @@ describe('IgnorableError', () => {
|
|
|
15
15
|
try {
|
|
16
16
|
throw new IgnorableError('Something bad happened!', 200);
|
|
17
17
|
} catch (error) {
|
|
18
|
-
expect(error.message).
|
|
19
|
-
expect(error.code).
|
|
18
|
+
expect(error.message).toEqual('Something bad happened!');
|
|
19
|
+
expect(error.code).toEqual(200);
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ describe('IgnorableError', () => {
|
|
|
24
24
|
try {
|
|
25
25
|
throw new IgnorableError('Something bad happened!');
|
|
26
26
|
} catch (error) {
|
|
27
|
-
expect(IgnorableError.isIgnorable(error)).
|
|
27
|
+
expect(IgnorableError.isIgnorable(error)).toEqual(true);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ProgramExecutor = require('./index-pubsub');
|
|
4
|
+
const ProgramsRepository = require('./repositories/programs');
|
|
5
|
+
const e2eTestReuse = require('@emartech/pubsub-client-js/e2e/e2e-test-resuse.js');
|
|
6
|
+
|
|
7
|
+
const knex = require('knex');
|
|
8
|
+
const DbCleaner = require('./test-helper/db-cleaner');
|
|
9
|
+
const { db } = require('./test-helper/get-test-db-config');
|
|
10
|
+
|
|
11
|
+
const customerId = 1234;
|
|
12
|
+
const topicName = 'program-executor';
|
|
13
|
+
|
|
14
|
+
const testJobLibrary = {
|
|
15
|
+
firstJob: {},
|
|
16
|
+
secondJob: {}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const waitForMessages = (timeout) => {
|
|
20
|
+
let timeOutLength = timeout;
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, timeOutLength));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('ProgramExecutor', function () {
|
|
25
|
+
let config;
|
|
26
|
+
let dbConnection;
|
|
27
|
+
let mockConstructorSpy;
|
|
28
|
+
let publisher;
|
|
29
|
+
|
|
30
|
+
beforeAll(async function () {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
dbConnection = knex({
|
|
33
|
+
client: 'pg',
|
|
34
|
+
connection: db.connection.connString
|
|
35
|
+
});
|
|
36
|
+
await DbCleaner.create(dbConnection).tearDown();
|
|
37
|
+
ProgramsRepository.create(dbConnection, 'programs');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterAll(async function () {
|
|
41
|
+
await DbCleaner.create(dbConnection).tearDown();
|
|
42
|
+
dbConnection.destroy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
await e2eTestReuse.afterEach(mockConstructorSpy, publisher);
|
|
48
|
+
|
|
49
|
+
publisher = undefined;
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
jest.resetModules();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
beforeEach(async function () {
|
|
56
|
+
|
|
57
|
+
config = {
|
|
58
|
+
knex: dbConnection,
|
|
59
|
+
tableName: 'programs',
|
|
60
|
+
topicName: topicName,
|
|
61
|
+
projectId: 'some-project-id'
|
|
62
|
+
};
|
|
63
|
+
const result = await e2eTestReuse.beforeEach(topicName);
|
|
64
|
+
mockConstructorSpy = result.mockConstructorSpy;
|
|
65
|
+
publisher = result.publisher;
|
|
66
|
+
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('#Program Executor', function () {
|
|
70
|
+
it('should execute program and maintain status in DB', async function () {
|
|
71
|
+
const runId = await ProgramExecutor.create(config).createProgram(
|
|
72
|
+
{
|
|
73
|
+
programData: {
|
|
74
|
+
customerId: customerId
|
|
75
|
+
},
|
|
76
|
+
jobs: ['current_program', 'next_program']
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
await ProgramExecutor.create(config).processPrograms(testJobLibrary);
|
|
80
|
+
|
|
81
|
+
await waitForMessages(1000);
|
|
82
|
+
|
|
83
|
+
const result = await dbConnection('programs').where({ run_id: runId }).first();
|
|
84
|
+
|
|
85
|
+
expect(result.run_id).toBe(runId);
|
|
86
|
+
expect(result.jobs).toEqual(['current_program', 'next_program']);
|
|
87
|
+
expect(result.step).toEqual(1);
|
|
88
|
+
expect(result.errored_at).toEqual(null);
|
|
89
|
+
expect(result.finished_at).not.toEqual(null);
|
|
90
|
+
expect(result.program_data).toEqual({ customerId: 1234 });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const consumer = require('@emartech/pubsub-client-js').Consumer;
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
|
|
6
|
+
const ProgramHandler = require('./program-handler');
|
|
7
|
+
const ProgramsRepository = require('./repositories/programs');
|
|
8
|
+
const QueueManager = require('./queue-manager-pubsub');
|
|
9
|
+
|
|
10
|
+
class ProgramExecutor extends EventEmitter {
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} config
|
|
13
|
+
* @param {object} config.knex - Connected Knex instance
|
|
14
|
+
* @param {string} config.projectId - GCP Project ID
|
|
15
|
+
* @param {string} config.tableName - Table name for bookkeeping
|
|
16
|
+
* @param {string} config.topicName - Topic name to publish to
|
|
17
|
+
*/
|
|
18
|
+
constructor(config) {
|
|
19
|
+
super();
|
|
20
|
+
this._config = config;
|
|
21
|
+
this._programsRepository = ProgramsRepository.create(config.knex, config.tableName);
|
|
22
|
+
this._queueManager = QueueManager.create(config.topicName, config.projectId);
|
|
23
|
+
this._programHandler = ProgramHandler.create(this._programsRepository, this._queueManager);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {object} data
|
|
28
|
+
* @param {object} data.programData
|
|
29
|
+
* @param {array} data.jobs
|
|
30
|
+
* @param {object} data.jobsData
|
|
31
|
+
*/
|
|
32
|
+
createProgram(data) {
|
|
33
|
+
return this._programHandler.createProgram(data);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
processPrograms(jobLibrary) {
|
|
37
|
+
const eventEmitter = this; // eslint-disable-line consistent-this
|
|
38
|
+
|
|
39
|
+
const programExecutorProcessor = require('./program-executor-processor').create(
|
|
40
|
+
this._programHandler,
|
|
41
|
+
this._queueManager,
|
|
42
|
+
jobLibrary
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
consumer
|
|
46
|
+
.create(this._config.topicName,
|
|
47
|
+
{
|
|
48
|
+
logger: `${this._config.topicName}-consumer`,
|
|
49
|
+
MaxStreams: 1,
|
|
50
|
+
MaxMessages: 1,
|
|
51
|
+
onMessage: async (message) => {
|
|
52
|
+
try {
|
|
53
|
+
await programExecutorProcessor.process(message);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
eventEmitter.emit('programError', { message, error });
|
|
56
|
+
error.message = error.message.substring(0, 255);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{ projectId: this._config.projectId }
|
|
62
|
+
)
|
|
63
|
+
.process();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {object} config
|
|
68
|
+
* @param {object} config.knex - Connected Knex instance
|
|
69
|
+
* @param {string} config.projectId - GCP Project ID
|
|
70
|
+
* @param {string} config.tableName - Table name for bookkeeping
|
|
71
|
+
* @param {string} config.topicName - Topic name to publish to
|
|
72
|
+
*/
|
|
73
|
+
static create(config) {
|
|
74
|
+
return new ProgramExecutor(config);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = ProgramExecutor;
|