@emartech/program-executor 3.11.1 → 3.12.1

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.
@@ -1,27 +1,26 @@
1
1
  'use strict';
2
2
 
3
- const QueueManager = require('./');
3
+ const QueueManager = require('.');
4
4
  const RabbitMq = require('@emartech/rabbitmq-client').RabbitMq;
5
-
5
+ const Logger = require('@emartech/json-logger').Logger;
6
6
  const testAmqpUrl = 'amqp://guest:guest@localhost:5672';
7
7
  const testChannelName = 'program-executor';
8
8
 
9
9
  describe('Queue-Manager', function () {
10
10
  describe('queueProgram', function () {
11
11
  it('should add proper queue item to the given channel', async function () {
12
- const rabbitMock = {
13
- insert: this.sandbox.stub().resolves(true),
14
- waitForConfirms: this.sandbox.stub().resolves(true)
15
- };
16
-
17
- this.sandbox.stub(RabbitMq, 'create').resolves(rabbitMock);
12
+ const insertMock = jest.fn();
13
+ RabbitMq.create = jest.fn().mockResolvedValue({
14
+ insert: insertMock,
15
+ waitForConfirms: jest.fn().mockResolvedValue(true)
16
+ });
18
17
 
19
18
  const queueManager = new QueueManager(testAmqpUrl, testChannelName);
20
19
  const queueData = { test_data: 123 };
21
20
 
22
21
  await queueManager.queueProgram(queueData);
23
22
 
24
- expect(RabbitMq.create).to.have.been.calledWith(
23
+ expect(RabbitMq.create).toHaveBeenCalledWith(
25
24
  { programExecutor: { url: testAmqpUrl, useConfirmChannel: true } },
26
25
  testChannelName,
27
26
  'programExecutor',
@@ -30,24 +29,22 @@ describe('Queue-Manager', function () {
30
29
  deadLetterRoutingKey: `${testChannelName}-retry-1000`
31
30
  }
32
31
  );
33
- expect(rabbitMock.insert).to.have.been.calledWith(queueData);
32
+ expect(insertMock).toHaveBeenCalledWith(queueData, expect.anything());
34
33
  });
35
34
 
36
35
  it('should log if confirmation fails instead of throwing', async function () {
37
36
  const errorToThrow = new Error('Boom!');
38
- const rabbitMock = {
39
- insert: this.sandbox.stub().resolves(true),
40
- waitForConfirms: this.sandbox.stub().rejects(errorToThrow)
41
- };
42
-
43
- this.sandbox.stub(RabbitMq, 'create').resolves(rabbitMock);
37
+ Logger.prototype.fromError = jest.fn();
38
+ const insertMock = jest.fn();
39
+ RabbitMq.create = jest.fn().mockResolvedValue({
40
+ insert: insertMock,
41
+ waitForConfirms: jest.fn(() => { throw errorToThrow; })
42
+ });
44
43
 
45
44
  const queueManager = new QueueManager(testAmqpUrl, testChannelName);
46
45
  const queueData = { test_data: 123 };
47
-
48
46
  await queueManager.queueProgram(queueData);
49
-
50
- expect(this.formErrorStub).to.have.been.calledWith('confirm-error', errorToThrow);
47
+ expect(Logger.prototype.fromError ).toHaveBeenCalledWith('confirm-error', errorToThrow, {queue_data: JSON.stringify(queueData)});
51
48
  });
52
49
  });
53
50
  });
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const PubSubClient = require('@emartech/pubsub-client-js').PubSubClient;
4
+ const logger = require('@emartech/json-logger')('program-executor-queue-manager-pubsub');
5
+
6
+ class QueueManager {
7
+ constructor(topicName, projectId, gcpKeyFileName) {
8
+ this._gcpKeyFileName = gcpKeyFileName;
9
+ this._projectId = projectId;
10
+ this._topicName = topicName;
11
+ }
12
+
13
+ async queueProgram(queueData) {
14
+ try {
15
+ const pubsub = await PubSubClient.create(
16
+ this._topicName, this._projectId, this._gcpKeyFileName
17
+ );
18
+
19
+ await pubsub.insert(queueData);
20
+
21
+ } catch (error) {
22
+ logger.fromError('queue-error', error, { queue_data: JSON.stringify(queueData) });
23
+ PubSubClient.clearPublisherCache();
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ static create(topicName, projectId, gcpKeyFileName) {
29
+ return new QueueManager(topicName, projectId, gcpKeyFileName);
30
+ }
31
+ }
32
+
33
+ module.exports = QueueManager;
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const QueueManager = require('.');
4
+ const PubSubClient = require('@emartech/pubsub-client-js').PubSubClient;
5
+ const Logger = require('@emartech/json-logger').Logger;
6
+ const testProjectId = 'test-project';
7
+ const testGcpKeyFileName = { key: 'test-key-file.json' };
8
+ const testTopicName = 'program-executor';
9
+
10
+ describe('Queue-Manager', function () {
11
+ describe('queueProgram', function () {
12
+ it('should add proper queue item to the given topic', async function () {
13
+ const insertMock = jest.fn();
14
+ PubSubClient.create = jest.fn().mockResolvedValue({
15
+ insert: insertMock
16
+ });
17
+
18
+ const queueManager = new QueueManager(testTopicName, testProjectId, testGcpKeyFileName);
19
+ const queueData = { test_data: 123 };
20
+
21
+ await queueManager.queueProgram(queueData);
22
+
23
+ expect(PubSubClient.create).toHaveBeenCalledWith(
24
+ testTopicName, testProjectId, testGcpKeyFileName
25
+ );
26
+ expect(insertMock).toHaveBeenCalledWith(queueData);
27
+ });
28
+
29
+ it('should log if confirmation fails instead of throwing', async function () {
30
+ const errorToThrow = new Error('Boom!');
31
+ Logger.prototype.fromError = jest.fn();
32
+ const insertMock = jest.fn().mockRejectedValue(errorToThrow);
33
+ PubSubClient.create = jest.fn().mockResolvedValue({
34
+ insert: insertMock
35
+ });
36
+
37
+ const queueManager = new QueueManager(testTopicName, testProjectId, testGcpKeyFileName);
38
+ const queueData = { test_data: 123 };
39
+ await expect(queueManager.queueProgram(queueData)).rejects.toThrow(errorToThrow);
40
+ expect(Logger.prototype.fromError)
41
+ .toHaveBeenCalledWith('queue-error', errorToThrow, { queue_data: JSON.stringify(queueData) });
42
+ });
43
+ });
44
+ });
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const ProgramsRepository = require('./programs');
4
+ const knex = require('knex');
5
+ const DbCleaner = require('../test-helper/db-cleaner');
6
+ const { db } = require('../test-helper/get-test-db-config');
4
7
 
5
8
  const programData = {
6
9
  customerId: 1,
@@ -12,18 +15,31 @@ const runId = '9123434';
12
15
  describe('ProgramsRepository', () => {
13
16
  let programsRepository;
14
17
  let resetUpdatedAt;
18
+ let dbConnection;
19
+
20
+ beforeAll(function () {
21
+
22
+ dbConnection = knex({
23
+ client: 'pg',
24
+ connection: db.connection.connString
25
+ });
15
26
 
16
- before(function () {
17
27
  resetUpdatedAt = async (runId) => {
18
28
  const oldDate = new Date(0);
19
- await this.db('programs').where({ run_id: runId }).update({ updated_at: oldDate });
29
+ await dbConnection('programs').where({ run_id: runId }).update({ updated_at: oldDate });
20
30
 
21
31
  return oldDate;
22
32
  };
23
33
  });
24
34
 
25
- beforeEach(function () {
26
- programsRepository = ProgramsRepository.create(this.db, 'programs');
35
+ afterAll(async function () {
36
+ await DbCleaner.create(dbConnection).tearDown();
37
+ dbConnection.destroy();
38
+ });
39
+
40
+ beforeEach(async function () {
41
+ await DbCleaner.create(dbConnection).tearDown();
42
+ programsRepository = ProgramsRepository.create(dbConnection, 'programs');
27
43
  });
28
44
 
29
45
  describe('#save', () => {
@@ -48,7 +64,7 @@ describe('ProgramsRepository', () => {
48
64
 
49
65
  const result = await programsRepository.getProgramByRunId(runId);
50
66
 
51
- expect(result).to.containSubset({
67
+ expect(result).toEqual(expect.objectContaining({
52
68
  runId,
53
69
  programData,
54
70
  jobs,
@@ -58,12 +74,12 @@ describe('ProgramsRepository', () => {
58
74
  erroredAt,
59
75
  errorMessage,
60
76
  stepRetryCount
61
- });
77
+ }));
62
78
  });
63
79
 
64
80
  it('creates table if not exists and saves program', async function () {
65
81
  await programsRepository.save({ runId, programData, jobs: ['a'] });
66
- await this.db.schema.dropTable('programs');
82
+ await dbConnection.schema.dropTable('programs');
67
83
 
68
84
  const jobs = ['a', 'b'];
69
85
  const jobData = { product_sync: { page: 40 } };
@@ -71,13 +87,13 @@ describe('ProgramsRepository', () => {
71
87
 
72
88
  const result = await programsRepository.getProgramByRunId(runId);
73
89
 
74
- expect(result).to.containSubset({
90
+ expect(result).toEqual(expect.objectContaining({
75
91
  runId,
76
92
  jobs,
77
93
  jobData,
78
94
  programData,
79
95
  step: 0
80
- });
96
+ }));
81
97
  });
82
98
  });
83
99
 
@@ -91,9 +107,9 @@ describe('ProgramsRepository', () => {
91
107
  await programsRepository.finishProgram(runId);
92
108
 
93
109
  const result = await programsRepository.getProgramByRunId(runId);
94
- expect(result.finishedAt).not.to.eql(null);
95
- expect(result.stepRetryCount).to.equal(0);
96
- expect(result.updatedAt.toString()).not.to.equal(oldDate.toString());
110
+ expect(result.finishedAt).not.toEqual(null);
111
+ expect(result.stepRetryCount).toEqual(0);
112
+ expect(result.updatedAt.toString()).not.toEqual(oldDate.toString());
97
113
  });
98
114
  });
99
115
 
@@ -106,10 +122,10 @@ describe('ProgramsRepository', () => {
106
122
  await programsRepository.setProgramToError(runId, 'Something wrong happened!');
107
123
 
108
124
  const result = await programsRepository.getProgramByRunId(runId);
109
- expect(result.finishedAt).to.eql(null);
110
- expect(result.erroredAt).not.to.eql(null);
111
- expect(result.errorMessage).to.eql('Something wrong happened!');
112
- expect(result.updatedAt.toString()).not.to.equal(oldDate.toString());
125
+ expect(result.finishedAt).toEqual(null);
126
+ expect(result.erroredAt).not.toEqual(null);
127
+ expect(result.errorMessage).toEqual('Something wrong happened!');
128
+ expect(result.updatedAt.toString()).not.toEqual(oldDate.toString());
113
129
  });
114
130
 
115
131
  it('should set and error_message and updated_at without setting finished_at or errored_at', async function () {
@@ -120,10 +136,10 @@ describe('ProgramsRepository', () => {
120
136
  await programsRepository.setProgramToError(runId, 'Something wrong happened!', false);
121
137
 
122
138
  const result = await programsRepository.getProgramByRunId(runId);
123
- expect(result.finishedAt).to.eql(null);
124
- expect(result.erroredAt).to.eql(null);
125
- expect(result.errorMessage).to.eql('Something wrong happened!');
126
- expect(result.updatedAt.toString()).not.to.equal(oldDate.toString());
139
+ expect(result.finishedAt).toEqual(null);
140
+ expect(result.erroredAt).toEqual(null);
141
+ expect(result.errorMessage).toEqual('Something wrong happened!');
142
+ expect(result.updatedAt.toString()).not.toEqual(oldDate.toString());
127
143
  });
128
144
 
129
145
  it('should trim error message', async function () {
@@ -138,7 +154,7 @@ describe('ProgramsRepository', () => {
138
154
  errorThrown = error;
139
155
  }
140
156
 
141
- expect(errorThrown).to.be.undefined;
157
+ expect(errorThrown).toBe(undefined);
142
158
  });
143
159
  });
144
160
 
@@ -148,14 +164,14 @@ describe('ProgramsRepository', () => {
148
164
  await programsRepository.incrementStepRetryCount(runId);
149
165
 
150
166
  const result = await programsRepository.getProgramByRunId(runId);
151
- expect(result.step).to.eql(0);
167
+ expect(result.step).toEqual(0);
152
168
 
153
169
  const oldDate = await resetUpdatedAt(runId);
154
170
  await programsRepository.incrementStep(runId);
155
171
  const incrementedResult = await programsRepository.getProgramByRunId(runId);
156
- expect(incrementedResult.step).to.equal(1);
157
- expect(incrementedResult.stepRetryCount).to.equal(0);
158
- expect(incrementedResult.updatedAt.toString()).not.to.equal(oldDate.toString());
172
+ expect(incrementedResult.step).toEqual(1);
173
+ expect(incrementedResult.stepRetryCount).toEqual(0);
174
+ expect(incrementedResult.updatedAt.toString()).not.toEqual(oldDate.toString());
159
175
  });
160
176
  });
161
177
 
@@ -163,13 +179,13 @@ describe('ProgramsRepository', () => {
163
179
  it('should increment step retry counter', async function () {
164
180
  await programsRepository.save({ runId, programData, jobs: ['a'] });
165
181
  const result = await programsRepository.getProgramByRunId(runId);
166
- expect(result.stepRetryCount).to.eql(0);
182
+ expect(result.stepRetryCount).toBe(0);
167
183
 
168
184
  const oldDate = await resetUpdatedAt(runId);
169
185
  await programsRepository.incrementStepRetryCount(runId);
170
186
  const incrementedResult = await programsRepository.getProgramByRunId(runId);
171
- expect(incrementedResult.stepRetryCount).to.equal(1);
172
- expect(incrementedResult.updatedAt.toString()).not.to.equal(oldDate.toString());
187
+ expect(incrementedResult.stepRetryCount).toBe(1);
188
+ expect(incrementedResult.updatedAt.toString()).not.toEqual(oldDate.toString());
173
189
  });
174
190
  });
175
191
 
@@ -180,17 +196,18 @@ describe('ProgramsRepository', () => {
180
196
 
181
197
  const result = await programsRepository.getProgramByRunId(runId);
182
198
 
183
- expect(result).to.containSubset({
199
+ expect(result).toEqual(expect.objectContaining({
184
200
  runId,
185
201
  jobs,
186
202
  step: 0
187
- });
203
+ }));
188
204
  });
189
205
 
190
206
  it('throws an error if program not found', async function () {
191
207
  await programsRepository.save({ runId, programData, jobs: [] });
192
208
 
193
- await expect(programsRepository.getProgramByRunId('NON_EXISTING_RUN_ID')).to.be.rejected;
209
+ await expect(programsRepository.getProgramByRunId('NON_EXISTING_RUN_ID')).rejects
210
+ .toThrow('Program not found for NON_EXISTING_RUN_ID.');
194
211
  });
195
212
  });
196
213
 
@@ -203,11 +220,11 @@ describe('ProgramsRepository', () => {
203
220
  await programsRepository.setJobDataByRunId(runId, { product_sync: { page: 1 } });
204
221
  const result = await programsRepository.getProgramByRunId(runId);
205
222
 
206
- expect(result).to.containSubset({
223
+ expect(result).toEqual(expect.objectContaining({
207
224
  runId: runId,
208
225
  jobData: { product_sync: { page: 1 } }
209
- });
210
- expect(result.updatedAt.toString()).not.to.equal(oldDate.toString());
226
+ }));
227
+ expect(result.updatedAt.toString()).not.toEqual(oldDate.toString());
211
228
  });
212
229
  });
213
230
 
@@ -221,10 +238,10 @@ describe('ProgramsRepository', () => {
221
238
 
222
239
  const result = await programsRepository.getUnfinishedPrograms();
223
240
 
224
- expect(result.length).to.equal(1);
225
- expect(result[0]).to.containSubset({
241
+ expect(result.length).toEqual(1);
242
+ expect(result[0]).toEqual(expect.objectContaining(({
226
243
  runId: '2'
227
- });
244
+ })));
228
245
  });
229
246
  });
230
247
  });
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const RetryableError = require('./');
3
+ const RetryableError = require('.');
4
4
 
5
5
  describe('RetryableError', () => {
6
6
  it('should have a retryable property set to true', () => {
7
7
  try {
8
8
  throw new RetryableError();
9
9
  } catch (error) {
10
- expect(error.retryable).to.be.true;
10
+ expect(error.retryable).toBe(true);
11
11
  }
12
12
  });
13
13
 
@@ -15,8 +15,8 @@ describe('RetryableError', () => {
15
15
  try {
16
16
  throw new RetryableError('Something bad happened!', 200);
17
17
  } catch (error) {
18
- expect(error.message).to.eql('Something bad happened!');
19
- expect(error.code).to.eql(200);
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('RetryableError', () => {
24
24
  try {
25
25
  throw new RetryableError('Something bad happened!');
26
26
  } catch (error) {
27
- expect(RetryableError.isRetryable(error)).to.eql(true);
27
+ expect(RetryableError.isRetryable(error)).toEqual(true);
28
28
  }
29
29
  });
30
30
  });
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const RunIdGenerator = require('./');
3
+ const RunIdGenerator = require('.');
4
4
 
5
5
  describe('RunId Generator', () => {
6
6
  describe('generate', () => {
7
7
  it('should return a string', () => {
8
8
  const result = RunIdGenerator.generate();
9
- expect(result).to.be.a('string');
9
+ expect(typeof result).toBe('string');
10
10
  });
11
11
  });
12
12
  });
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const getDbConfig = function () {
4
+ let database = process.env.POSTGRES_DATABASE;
5
+
6
+ if (process.env.JEST_WORKER_ID) {
7
+ database = `${database}_${process.env.JEST_WORKER_ID}`;
8
+ }
9
+
10
+ const port = process.env.POSTGRES_PORT;
11
+
12
+ return {
13
+ user: process.env.POSTGRES_USER,
14
+ host: process.env.POSTGRES_HOST,
15
+ database,
16
+ password: process.env.POSTGRES_PASSWORD,
17
+ port,
18
+ connString: `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}` +
19
+ `@${process.env.POSTGRES_HOST}:${port}/${database}`
20
+ };
21
+ };
22
+
23
+ module.exports = {
24
+ db: {
25
+ connection: {
26
+ ...getDbConfig(process.env.NODE_ENV)
27
+ }
28
+ }
29
+ };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ require('dotenv').config({ silent: true });
4
+
5
+ const { PubSub } = require('@google-cloud/pubsub');
6
+ const { db } = require('./test-helper/get-test-db-config');
7
+ const knex = require('knex');
8
+ const DbCleaner = require('./test-helper/db-cleaner');
9
+
10
+ module.exports = async () => {
11
+ const dbConnection = knex({
12
+ client: 'pg',
13
+ connection: db.connection.connString
14
+ });
15
+
16
+ await DbCleaner.create(dbConnection).tearDown();
17
+
18
+ dbConnection.destroy();
19
+
20
+ const _pubsub = new PubSub();
21
+ await _pubsub.createTopic('program-executor');
22
+ await _pubsub.topic('program-executor').createSubscription('program-executor');
23
+ };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ require('dotenv').config({ silent: true });
4
+ const { PubSub } = require('@google-cloud/pubsub');
5
+
6
+
7
+ module.exports = async () => {
8
+ console.log('global teardown');
9
+
10
+ const _pubsub = new PubSub();
11
+ await _pubsub.topic('program-executor').delete();
12
+ await _pubsub.subscription('program-executor').delete();
13
+ };
package/src/setup.spec.js DELETED
@@ -1,50 +0,0 @@
1
- 'use strict';
2
-
3
- require('dotenv').config({ silent: true });
4
-
5
- const knex = require('knex');
6
- const sinon = require('sinon');
7
- const chai = require('chai');
8
- const sinonChai = require('sinon-chai');
9
- const chaiSubset = require('chai-subset');
10
- const chaiString = require('chai-string');
11
- const chaiAsPromised = require('chai-as-promised');
12
- const Logger = require('@emartech/json-logger').Logger;
13
-
14
- const DbCleaner = require('./test-helper/db-cleaner');
15
-
16
- chai.use(chaiSubset);
17
- chai.use(sinonChai);
18
- chai.use(chaiString);
19
- chai.use(chaiAsPromised);
20
-
21
- global.expect = chai.expect;
22
-
23
- before(function () {
24
- this.db = knex({
25
- client: 'pg',
26
- connection: process.env.DATABASE_URL || process.env.DATABASE_TEST_URL
27
- });
28
- });
29
-
30
- beforeEach(async function () {
31
- this.sinon = sinon;
32
- this.sandbox = sinon.createSandbox();
33
-
34
- this.loggerLog = this.sandbox.stub(Logger.prototype, 'error');
35
-
36
- this.sandbox.stub(Logger.prototype, 'trace');
37
- this.sandbox.stub(Logger.prototype, 'debug');
38
- this.infoStub = this.sandbox.stub(Logger.prototype, 'info');
39
- this.warnStub = this.sandbox.stub(Logger.prototype, 'warn');
40
- this.formErrorStub = this.sandbox.stub(Logger.prototype, 'fromError');
41
- this.sandbox.stub(Logger.prototype, 'warnFromError');
42
- this.sandbox.stub(Logger.prototype, 'fatal');
43
-
44
- await DbCleaner.create(this.db).tearDown();
45
- });
46
-
47
- afterEach(async function () {
48
- this.sandbox.restore();
49
- await DbCleaner.create(this.db).tearDown();
50
- });