ti2 1.0.93 → 1.0.95

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.
@@ -7,7 +7,7 @@ jobs:
7
7
  timeout-minutes: 10
8
8
  runs-on: ubuntu-latest
9
9
  container:
10
- image: node:16.15.0
10
+ image: node:16.20.0
11
11
  services:
12
12
  mysql:
13
13
  image: mysql
@@ -31,13 +31,17 @@ jobs:
31
31
  - uses: actions/checkout@v2
32
32
  - uses: actions/setup-node@v1
33
33
  with:
34
- node-version: 12
34
+ node-version: 16
35
35
  - name: install dependencies
36
36
  run: npm ci
37
37
  - name: create/migrate the database
38
38
  run: npx sequelize db:create && npx sequelize db:migrate
39
39
  - name: seed the database
40
40
  run: npx sequelize db:seed:all
41
- - run: RUNNER_TRACKING_ID="" && (nohup node ./test/worker.js&)
41
+ - name: start worker process
42
+ run: RUNNER_TRACKING_ID="" node ./test/worker.js > worker.log 2>&1 &
42
43
  - name: run the tests
43
44
  run: npx jest -i --coverage --forceExit
45
+ - name: display worker logs
46
+ if: ${{ failure() }}
47
+ run: cat worker.log
package/api.yml CHANGED
@@ -13,6 +13,78 @@ components:
13
13
  scheme: bearer
14
14
  bearerFormat: JWT
15
15
  schemas:
16
+ CronJob:
17
+ type: object
18
+ properties:
19
+ id:
20
+ type: string
21
+ description: uuid of the crojob to remove
22
+ example: '550e8400-e29b-41d4-a716-446655440000'
23
+ method:
24
+ type: string
25
+ description: HTTP method for the API call
26
+ example: 'POST'
27
+ enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
28
+ url:
29
+ type: string
30
+ description: URL path for the API call
31
+ example: '/products/travelgate-hotelx/123/search'
32
+ cron:
33
+ type: string
34
+ description: Cron expression for job scheduling
35
+ example: '0 0 * * *'
36
+ userId:
37
+ type: string
38
+ description: User ID associated with the job
39
+ body:
40
+ type: object
41
+ description: body for the scheduled rquest
42
+ removeOnComplete:
43
+ type: boolean
44
+ description: Whether the job should be removed from the queue after successful completion (for one-off executions).
45
+ default: false
46
+ CronJobList:
47
+ type: object
48
+ properties:
49
+ jobs:
50
+ type: array
51
+ items:
52
+ $ref: '#/components/schemas/CronJob'
53
+ CronJobCreate:
54
+ type: object
55
+ required:
56
+ - method
57
+ - url
58
+ - cron
59
+ properties:
60
+ method:
61
+ type: string
62
+ description: HTTP method for the API call
63
+ example: 'POST'
64
+ enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
65
+ url:
66
+ type: string
67
+ description: URL path for the API call
68
+ example: '/products/travelgate-hotelx/123/search'
69
+ cron:
70
+ type: string
71
+ description: Cron expression for job scheduling
72
+ example: '0 0 * * *'
73
+ callbackUrl:
74
+ type: string
75
+ description: Optional URL to call after job completion
76
+ example: 'https://example.com/callback'
77
+ payload:
78
+ type: object
79
+ description: Optional payload for the API call
80
+ example:
81
+ startDate: '01/01/2023'
82
+ endDate: '03/04/2023'
83
+ keyPath: 'MAGLUX|7CQLDACMAGLUXDELSUI'
84
+ removeOnComplete:
85
+ type: boolean
86
+ description: Whether the job should be removed from the queue after successful completion (for one-off executions).
87
+ default: false
16
88
  ServerInfo:
17
89
  type: object
18
90
  properties:
@@ -404,6 +476,93 @@ components:
404
476
  type: boolean
405
477
  default: false
406
478
  paths:
479
+ /cronjobs/{userId}:
480
+ get:
481
+ security:
482
+ - bearerAuth: ['admin', 'user']
483
+ tags:
484
+ - cronjobs
485
+ summary: List cronjobs for a user
486
+ operationId: listCronjobs
487
+ parameters:
488
+ - name: userId
489
+ in: path
490
+ description: User ID to list cronjobs for
491
+ required: true
492
+ schema:
493
+ type: string
494
+ responses:
495
+ '200':
496
+ description: List of cronjobs
497
+ content:
498
+ application/json:
499
+ schema:
500
+ $ref: '#/components/schemas/CronJobList'
501
+ post:
502
+ security:
503
+ - bearerAuth: ['admin', 'user']
504
+ tags:
505
+ - cronjobs
506
+ summary: Create a new cronjob
507
+ operationId: createCronjob
508
+ parameters:
509
+ - name: userId
510
+ in: path
511
+ description: User ID to create cronjob for
512
+ required: true
513
+ schema:
514
+ type: string
515
+ requestBody:
516
+ required: true
517
+ content:
518
+ application/json:
519
+ schema:
520
+ $ref: '#/components/schemas/CronJobCreate'
521
+ responses:
522
+ '200':
523
+ description: Created cronjob
524
+ content:
525
+ application/json:
526
+ schema:
527
+ $ref: '#/components/schemas/CronJob'
528
+ /cronjobs/{userId}/{id}:
529
+ delete:
530
+ security:
531
+ - bearerAuth: ['admin', 'user']
532
+ tags:
533
+ - cronjobs
534
+ summary: Delete a cronjob
535
+ operationId: deleteCronjob
536
+ parameters:
537
+ - name: userId
538
+ in: path
539
+ description: User ID the cronjob belongs to
540
+ required: true
541
+ schema:
542
+ type: string
543
+ - name: id
544
+ in: path
545
+ description: Cron job ID (UUID) to delete
546
+ required: true
547
+ schema:
548
+ type: string
549
+ responses:
550
+ '404':
551
+ description: Cronjob not found
552
+ content:
553
+ application/json:
554
+ schema:
555
+ type: object
556
+ properties:
557
+ message:
558
+ type: string
559
+ example: 'Cronjob not found'
560
+ '200':
561
+ description: Success status
562
+ content:
563
+ application/json:
564
+ schema:
565
+ $ref: '#/components/schemas/Status'
407
566
  /ping:
408
567
  get:
409
568
  tags:
package/cli.js CHANGED
@@ -5,6 +5,7 @@ const { Command, Argument } = require('commander');
5
5
  const { migrateApp } = require('./controllers/app')();
6
6
  const { migrate } = require('./controllers/admin');
7
7
  const { decrypt, encrypt } = require('./lib/security');
8
+ const { queue, redisResults } = require('./worker/queue');
8
9
 
9
10
  const program = new Command();
10
11
 
@@ -55,4 +56,109 @@ program.command('encrypt')
55
56
  process.exit(0);
56
57
  });
57
58
 
59
+ program.command('queue:list [pattern]')
60
+ .description('List jobs in the queue, optionally filtering by ID pattern (e.g., "jobId*", "*suffix", "prefix*suffix")')
61
+ .action(async (pattern) => {
62
+ try {
63
+ await queue.isReady(); // Ensure queue is ready
64
+ const jobTypes = ['waiting', 'active', 'completed', 'failed', 'delayed', 'paused'];
65
+ let message = `Fetching jobs of types: ${jobTypes.join(', ')}...`;
66
+ if (pattern) {
67
+ message += ` matching pattern "${pattern}"`;
68
+ }
69
+ console.log(message);
70
+ let jobs = await queue.getJobs(jobTypes);
71
+
72
+ if (pattern) {
73
+ const regexPatternText = '^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
74
+ const regexPattern = new RegExp(regexPatternText);
75
+ console.log(`Using filter regex: ${regexPattern}`);
76
+ jobs = jobs.filter(job => regexPattern.test(job.id.toString()));
77
+ }
78
+
79
+ if (jobs.length === 0) {
80
+ if (pattern) {
81
+ console.log(`No jobs found matching pattern "${pattern}".`);
82
+ } else {
83
+ console.log('No jobs found in the queue.');
84
+ }
85
+ } else {
86
+ console.log(`Found ${jobs.length} jobs:`);
87
+ for (const job of jobs) {
88
+ const state = await job.getState();
89
+ const progress = job.progress();
90
+ const timestamp = new Date(job.timestamp).toISOString();
91
+ console.log(`\n- ID: ${job.id}`);
92
+ console.log(` State: ${state}`);
93
+ console.log(` Progress: ${progress}`);
94
+ console.log(` Added: ${timestamp}`);
95
+ console.log(` Name (Queue): ${job.name}`);
96
+ console.log(` Data: ${JSON.stringify(job.data, null, 2)}`);
97
+ if (job.opts.repeat) {
98
+ console.log(` Repeat Options: ${JSON.stringify(job.opts.repeat)}`);
99
+ }
100
+ if (state === 'failed' && job.failedReason) {
101
+ console.log(` Failed Reason: ${job.failedReason}`);
102
+ }
103
+ }
104
+ }
105
+ } catch (error) {
106
+ console.error('Error listing queue jobs:', error);
107
+ process.exit(1);
108
+ }
109
+ process.exit(0);
110
+ });
111
+
112
+ program.command('queue:remove')
113
+ .description('Remove job instances from the queue by ID or pattern (e.g., "jobId*", "*suffix", "prefix*suffix")')
114
+ .argument('<pattern>', 'Job ID or glob-like pattern for job IDs to remove')
115
+ .action(async (pattern) => {
116
+ try {
117
+ await queue.isReady(); // Ensure queue is ready
118
+ console.log(`Attempting to remove job instances matching pattern: "${pattern}"`);
119
+
120
+ const jobTypes = ['waiting', 'active', 'completed', 'failed', 'delayed', 'paused'];
121
+ const jobs = await queue.getJobs(jobTypes);
122
+ let removedCount = 0;
123
+ const removedJobIds = [];
124
+
125
+ const regexPatternText = '^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
126
+ const regexPattern = new RegExp(regexPatternText);
127
+
128
+ console.log(`Using regex: ${regexPattern}`);
129
+
130
+ for (const job of jobs) {
131
+ if (regexPattern.test(job.id.toString())) {
132
+ try {
133
+ await job.remove();
134
+ removedJobIds.push(job.id.toString());
135
+ removedCount++;
136
+ console.log(`Removed job ${job.id} from Bull queue.`);
137
+ } catch (e) {
138
+ console.error(`Failed to remove job ${job.id} from Bull queue:`, e.message);
139
+ }
140
+ }
141
+ }
142
+
143
+ if (removedCount > 0) {
144
+ console.log(`Successfully removed ${removedCount} job instance(s) from Bull queue.`);
145
+ if (removedJobIds.length > 0) {
146
+ console.log(`Attempting to remove ${removedJobIds.length} result(s) from Redis for the removed jobs...`);
147
+ try {
148
+ const delCount = await redisResults.del(removedJobIds);
149
+ console.log(`Removed ${delCount} result(s) from Redis.`);
150
+ } catch (e) {
151
+ console.error('Failed to remove results from Redis:', e.message);
152
+ }
153
+ }
154
+ } else {
155
+ console.log(`No job instances found matching pattern "${pattern}".`);
156
+ }
157
+ } catch (error) {
158
+ console.error('Error removing queue jobs:', error);
159
+ process.exit(1);
160
+ }
161
+ process.exit(0);
162
+ });
163
+
58
164
  program.parse();
@@ -1,12 +1,12 @@
1
1
  /* globals beforeAll describe it expect */
2
2
 
3
3
  const chance = require('chance').Chance();
4
- const testUtils = require('../../test/utils');
5
4
  const slugify = require('../../test/slugify');
6
5
 
7
6
  const { env: { adminKey } } = process;
8
7
 
9
8
  describe('admin', () => {
9
+ const testUtils = require('../../test/utils');
10
10
  const appName = slugify(
11
11
  chance.company(),
12
12
  );
@@ -3,11 +3,10 @@ const axios = require('axios');
3
3
  const MockAdapter = require('axios-mock-adapter');
4
4
  const chance = require('chance').Chance();
5
5
 
6
- const testUtils = require('../../test/utils');
7
-
8
6
  const { env: { adminKey } } = process;
9
7
 
10
8
  describe('allotment', () => {
9
+ const testUtils = require('../../test/utils');
11
10
  const newApp = {
12
11
  name: 'tourplantest',
13
12
  packageName: 'ti2-tourplanTest',
@@ -3,13 +3,13 @@ const chance = require('chance').Chance();
3
3
  const jwt = require('jwt-promise');
4
4
  const R = require('ramda');
5
5
 
6
- const testUtils = require('../../test/utils');
7
6
  const slugify = require('../../test/slugify');
8
7
  let appController = require('../app');
9
8
 
10
9
  const { env: { adminKey, jwtSecret } } = process;
11
10
 
12
11
  describe('app', () => {
12
+ const testUtils = require('../../test/utils');
13
13
  const appName = slugify(
14
14
  chance.company(),
15
15
  );
@@ -2,12 +2,12 @@
2
2
 
3
3
  const chance = require('chance').Chance();
4
4
  const hash = require('object-hash');
5
- const testUtils = require('../../test/utils');
6
5
  const cache = require('../../cache');
7
6
 
8
7
  const { env: { adminKey } } = process;
9
8
 
10
9
  describe('user: bookings controller', () => {
10
+ const testUtils = require('../../test/utils');
11
11
  const newApp = {
12
12
  name: 'travelgate',
13
13
  packageName: 'ti2-travelgate',
@@ -344,8 +344,18 @@ describe('user: bookings controller', () => {
344
344
  expect(products.length).toBe(2); // Based on existing test expectations
345
345
  });
346
346
 
347
- // Plugin should only be called once due to lock mechanism
348
- expect(plugins[0].searchProducts).toHaveBeenCalledTimes(1);
347
+ // Plugin should only be called once due to lock mechanism, and with correct context
348
+ expect(plugins[0].searchProducts).toHaveBeenCalledWith(
349
+ expect.objectContaining({
350
+ token: expect.objectContaining({
351
+ client: 'tourconnect',
352
+ endpoint: expect.stringContaining('https://api.travelgatex.com'),
353
+ }),
354
+ userId: expect.stringContaining(userId),
355
+ payload: {},
356
+ typeDefsAndQueries: expect.any(Object)
357
+ })
358
+ );
349
359
  });
350
360
  });
351
361
  });