ti2 1.0.95 → 1.0.96

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.
@@ -30,12 +30,17 @@ describe('user: bookings controller', () => {
30
30
  let doApiPost;
31
31
  let plugins;
32
32
  let userToken;
33
+ let appSetup;
34
+ let createUserToken;
35
+
33
36
  beforeAll(async () => {
34
37
  ({
35
38
  doApiDelete,
36
39
  doApiGet,
37
40
  doApiPost,
38
41
  plugins,
42
+ appSetup,
43
+ createUserToken,
39
44
  } = await testUtils({
40
45
  plugins: [newApp.name],
41
46
  }));
@@ -55,40 +60,25 @@ describe('user: bookings controller', () => {
55
60
  pluginName: appKey,
56
61
  key: `${cacheKey}:lock`,
57
62
  });
58
- // create the travelgate app and user
59
- const { value: apiKey } = await doApiPost({
60
- url: '/app',
61
- token: adminKey,
62
- payload: newApp,
63
- });
64
- expect(apiKey).toBeTruthy();
65
-
66
- // Create the user token
67
- ({ value: userToken } = await doApiPost({
68
- url: '/user',
69
- token: adminKey,
70
- payload: { userId },
71
- }));
63
+
64
+ // Create user token
65
+ userToken = createUserToken(userId);
72
66
  expect(userToken).toBeTruthy();
73
- // Create the user first, then get the token
67
+
68
+ // Create the user
74
69
  await doApiPost({
75
70
  url: '/user',
76
- token: adminKey,
77
- payload: { userId, email: `${userId}@example.com` },
78
- });
79
- // Now get the token
80
- ({ value: userToken } = await doApiPost({
81
- url: '/user',
82
- token: adminKey,
83
- payload: { userId },
84
- }));
85
- expect(userToken).toBeTruthy();
71
+ token: adminKey,
72
+ payload: { userId, email: `${userId}@example.com` },
73
+ });
74
+
86
75
  // create the travelGage+User setup
87
76
  let userAppKeys = [];
88
77
  ({ userAppKeys } = await doApiGet({
89
78
  url: `/user/${userId}/apps`,
90
79
  token: userToken,
91
80
  }));
81
+
92
82
  // Check if we need to create the integration
93
83
  if (!userAppKeys.find(e => e.hint === newIntegration.tokenHint
94
84
  && e.integrationId === newApp.name)) {
@@ -96,268 +86,30 @@ describe('user: bookings controller', () => {
96
86
  const { value } = await doApiPost({
97
87
  url: `/${appKey}/${userId}`,
98
88
  token: userToken,
99
- payload: newIntegration,
100
- });
101
- expect(value).toBeTruthy();
102
- }
89
+ payload: newIntegration,
90
+ });
91
+ expect(value).toBeTruthy();
92
+ }
103
93
  });
104
94
  beforeEach(async () => {
105
95
  jest.clearAllMocks();
106
96
  });
107
- it('should be able to search a for a booking', async () => {
108
- const payload = {
109
- bookingId: '', supplierBookingId: '', name: '',
110
- };
111
- const { bookings } = await doApiPost({
112
- url: `/bookings/${appKey}/${userId}/testingToken/search`,
113
- token: userToken,
114
- payload,
115
- });
116
- expect(plugins[0].searchHotelBooking).toHaveBeenCalled();
117
- expect(Array.isArray(bookings)).toBeTruthy();
118
- expect(plugins[0].searchHotelBooking.mock.calls[0][0].payload).toEqual(payload);
119
- expect(plugins[0].searchHotelBooking.mock.calls[0][0].token).toEqual(token);
120
-
121
- // expect(Array.isArray(retVal.bookings)).toBeTruthy();
122
- // expect(retVal.bookings.length > 0).toBeTruthy();
123
- });
124
-
125
- describe('searchProducts', () => {
126
- it('should be able to get bookings products for most users without special setup: no cache', async () => {
127
- const payload = {};
128
- const { products } = await doApiPost({
129
- url: `/products/${appKey}/${userId}/testingToken/search`,
130
- token: userToken,
131
- payload,
132
- });
133
- expect(plugins[0].searchProducts).toHaveBeenCalled();
134
- expect(Array.isArray(products)).toBeTruthy();
135
- expect(products.length).toBe(2);
136
- expect(products[0].options.length).toBe(1);
137
- expect(products[1].options.length).toBe(2);
138
- });
139
- it('should be able to get booking products: no cache, forceRefresh', async () => {
140
- // NOTE: we need to remove the cache first
141
- const cacheKey = hash({
142
- appKey,
143
- userId,
144
- hint: 'testingToken',
145
- operationId: 'bookingsProductSearch',
146
- });
147
- await cache.drop({
148
- pluginName: appKey,
149
- key: cacheKey,
150
- });
97
+ describe('bookings', ()=> {
98
+ it('should be able to search a for a booking', async () => {
151
99
  const payload = {
152
- forceRefresh: true,
100
+ bookingId: '', supplierBookingId: '', name: '',
153
101
  };
154
- const { products } = await doApiPost({
155
- url: `/products/${appKey}/${userId}/testingToken/search`,
102
+ const { bookings } = await doApiPost({
103
+ url: `/bookings/${appKey}/${userId}/testingToken/search`,
156
104
  token: userToken,
157
105
  payload,
158
106
  });
159
- expect(plugins[0].searchProducts).toHaveBeenCalled();
160
- expect(Array.isArray(products)).toBeTruthy();
161
- expect(products.length).toBe(2);
162
- expect(products[0].options.length).toBe(1);
163
- expect(products[1].options.length).toBe(2);
164
- expect(plugins[0].searchProducts.mock.calls[0][0].token).toEqual(token);
107
+ expect(plugins[0].searchHotelBooking).toHaveBeenCalled();
108
+ expect(Array.isArray(bookings)).toBeTruthy();
109
+ expect(plugins[0].searchHotelBooking.mock.calls[0][0].payload).toEqual(payload);
110
+ expect(plugins[0].searchHotelBooking.mock.calls[0][0].token).toEqual(token);
165
111
  });
166
- it('should be able to get booking products: using cache', async () => {
167
- const payload = {};
168
- const { products } = await doApiPost({
169
- url: `/products/${appKey}/${userId}/testingToken/search`,
170
- token: userToken,
171
- payload,
172
- });
173
- expect(plugins[0].searchProducts).not.toHaveBeenCalled();
174
- expect(Array.isArray(products)).toBeTruthy();
175
- expect(products.length).toBe(2);
176
- expect(products[0].options.length).toBe(1);
177
- expect(products[1].options.length).toBe(2);
178
- });
179
- it('should be able to get booking products with searchInput', async () => {
180
112
 
181
- const payload = {
182
- searchInput: 'Transfer from Sydney Harbor to Hilton Hotel',
183
- };
184
- const { products } = await doApiPost({
185
- url: `/products/${appKey}/${userId}/testingToken/search`,
186
- token: userToken,
187
- payload,
188
- });
189
- expect(plugins[0].searchProducts).not.toHaveBeenCalled();
190
- expect(Array.isArray(products)).toBeTruthy();
191
- expect(products.length).toBe(1);
192
- expect(products[0].productName).toBe('Davids');
193
- expect(products[0].options.length).toBe(1);
194
- expect(products[0].options[0].optionName).toBe('Transfer from Sydney Harbor Bridge to Hilton Hotel');
195
- });
196
- it('should be able to process doNotCallPluginForProducts in token', async () => {
197
- const { userAppKeys } = await doApiGet({
198
- url: `/user/${userId}/apps`,
199
- token: userToken,
200
- });
201
- let newnewIntegration = userAppKeys.find(e => e.hint === 'hint_for_doNotCallPluginForProducts'
202
- && e.integrationId === newApp.name);
203
- if (!newnewIntegration) {
204
- const newIntegrationContent = {
205
- endpoint: 'https://api.travelgatex.com',
206
- apiKey: chance.guid(),
207
- client: 'tourconnect',
208
- doNotCallPluginForProducts: true,
209
- };
210
- // create a new user token with the new content
211
- newnewIntegration = await doApiPost({
212
- url: `/travelgate/${userId}`,
213
- token: adminKey,
214
- payload: {
215
- token: newIntegrationContent,
216
- tokenHint: 'hint_for_doNotCallPluginForProducts',
217
- },
218
- });
219
- }
220
- expect(newnewIntegration).toBeTruthy();
221
- // first time call, no cache expect no plugin call and empty products
222
- let products;
223
- await doApiPost({
224
- url: `/products/${appKey}/${userId}/hint_for_doNotCallPluginForProducts/search`,
225
- token: userToken,
226
- payload: {},
227
- }).then(({ products: p }) => {
228
- products = p;
229
- });
230
- expect(plugins[0].searchProducts).not.toHaveBeenCalled();
231
- expect(Array.isArray(products)).toBeTruthy();
232
- expect(products.length).toBe(0);
233
- // second time call, forceRefresh and still expect plugin call
234
- await doApiPost({
235
- url: `/products/${appKey}/${userId}/hint_for_doNotCallPluginForProducts/search`,
236
- token: userToken,
237
- payload: { forceRefresh: true },
238
- }).then(({ products: p }) => {
239
- products = p;
240
- });
241
- expect(plugins[0].searchProducts).toHaveBeenCalled();
242
- expect(Array.isArray(products)).toBeTruthy();
243
- expect(products.length).toBe(2);
244
- });
245
-
246
- describe('cache TTR and lock mechanism', () => {
247
- const shortTTRToken = {
248
- endpoint: 'https://api.travelgatex.com',
249
- apiKey: chance.guid(),
250
- client: 'tourconnect',
251
- ttlForProducts: 2, // 2 seconds TTR
252
- };
253
- beforeAll(async () => {
254
- await doApiPost({
255
- url: `/${appKey}/${userId}`,
256
- token: userToken,
257
- payload: {
258
- tokenHint: 'ttr-test',
259
- token: shortTTRToken,
260
- },
261
- });
262
- });
263
- beforeEach(async () => {
264
- jest.clearAllMocks();
265
- });
266
-
267
- it('should use cache within TTR period', async () => {
268
- await doApiPost({
269
- url: `/products/${appKey}/${userId}/ttr-test/search`,
270
- token: userToken,
271
- payload: {},
272
- });
273
- expect(plugins[0].searchProducts).toHaveBeenCalledTimes(1);
274
-
275
- // Second immediate call should use cache
276
- await doApiPost({
277
- url: `/products/${appKey}/${userId}/ttr-test/search`,
278
- token: userToken,
279
- payload: {},
280
- });
281
- expect(plugins[0].searchProducts).toHaveBeenCalledTimes(1); // Still 1, used cache
282
- });
283
-
284
- it('should refresh cache after TTR expires', async () => {
285
- // Wait for TTR to expire (2 seconds + buffer) so the cache from previous test is stale
286
- await new Promise(resolve => {
287
- setTimeout(resolve, 2100);
288
- });
289
-
290
- // First call to populate cache
291
- await doApiPost({
292
- url: `/products/${appKey}/${userId}/ttr-test/search`,
293
- token: userToken,
294
- payload: {},
295
- });
296
- expect(plugins[0].searchProducts).toHaveBeenCalledTimes(1);
297
-
298
- // Wait for TTR to expire (2 seconds + buffer)
299
- await new Promise(resolve => {
300
- setTimeout(resolve, 2100);
301
- });
302
-
303
- // Call after TTR expired should hit the plugin again
304
- await doApiPost({
305
- url: `/products/${appKey}/${userId}/ttr-test/search`,
306
- token: userToken,
307
- payload: {},
308
- });
309
- expect(plugins[0].searchProducts).toHaveBeenCalledTimes(2);
310
- });
311
-
312
- it('should use lock mechanism to prevent concurrent calls', async () => {
313
- // Wait for TTR to expire (2 seconds + buffer) so the cache from previous test is stale
314
- await new Promise(resolve => {
315
- setTimeout(resolve, 2100);
316
- });
317
-
318
- // Make multiple requests with slight delays to simulate real-world concurrent requests
319
- const makeRequest = () => doApiPost({
320
- url: `/products/${appKey}/${userId}/ttr-test/search`,
321
- token: userToken,
322
- payload: {},
323
- });
324
-
325
- // Start first request
326
- const request1 = makeRequest();
327
- // Start second request after 10ms
328
- await new Promise(resolve => {
329
- setTimeout(resolve, 10);
330
- });
331
- const request2 = makeRequest();
332
- // Start third request after another 10ms
333
- await new Promise(resolve => {
334
- setTimeout(resolve, 10);
335
- });
336
- const request3 = makeRequest();
337
-
338
- // Wait for all requests to complete
339
- const results = await Promise.all([request1, request2, request3]);
340
-
341
- // All requests should return valid products
342
- results.forEach(({ products }) => {
343
- expect(Array.isArray(products)).toBeTruthy();
344
- expect(products.length).toBe(2); // Based on existing test expectations
345
- });
346
-
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
- );
359
- });
360
- });
361
113
  });
362
114
 
363
115
  it('should be able to search for availabiility', async () => {
@@ -142,25 +142,28 @@ const createAppToken = async (req, res, next) => {
142
142
  if (existing) {
143
143
  const bullJob = await queue.getJob(job.bullJobId);
144
144
  if (!bullJob) {
145
- bullJobId = await addJob(jobPayload, jobParams);
146
- existing.bullJobId = bullJobId;
145
+ let rawBullJobId = await addJob(jobPayload, jobParams);
146
+ existing.bullJobId = (rawBullJobId && typeof rawBullJobId === 'object' && rawBullJobId.id) ? rawBullJobId.id : rawBullJobId;
147
147
  await existing.save();
148
148
  } else {
149
149
  // make sure the cron is the same
150
+ // bullJobId here is from existing.bullJobId, which should be a string.
151
+ // The queue.getJob expects a string ID.
150
152
  const bullCron = R.path(
151
153
  ['opts', 'repeat', 'cron'],
152
- await queue.getJob(bullJobId),
154
+ await queue.getJob(existing.bullJobId), // Use existing.bullJobId
153
155
  );
154
156
  if (bullCron !== job.cron) {
155
- await queue.removeJobs(bullJobId);
156
- bullJobId = await addJob(jobPayload, jobParams);
157
- existing.bullJobId = bullJobId;
157
+ await queue.removeJobs(existing.bullJobId); // Use existing.bullJobId
158
+ let rawBullJobId = await addJob(jobPayload, jobParams);
159
+ existing.bullJobId = (rawBullJobId && typeof rawBullJobId === 'object' && rawBullJobId.id) ? rawBullJobId.id : rawBullJobId;
158
160
  await existing.save();
159
161
  }
160
162
  }
161
163
  } else {
162
- bullJobId = await addJob(jobPayload, jobParams);
163
- await sqldb.CronJobs.create({ ...where, bullJobId });
164
+ let rawBullJobId = await addJob(jobPayload, jobParams);
165
+ const actualBullJobId = (rawBullJobId && typeof rawBullJobId === 'object' && rawBullJobId.id) ? rawBullJobId.id : rawBullJobId;
166
+ await sqldb.CronJobs.create({ ...where, bullJobId: actualBullJobId });
164
167
  }
165
168
  });
166
169
  }