ti2-ventrata 1.0.3 → 1.0.6

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.
Files changed (3) hide show
  1. package/index.js +94 -16
  2. package/index.test.js +51 -29
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -6,21 +6,30 @@ const moment = require('moment');
6
6
  const jwt = require('jsonwebtoken');
7
7
  const wildcardMatch = require('./utils/wildcardMatch');
8
8
 
9
+ const CONCURRENCY = 3; // is this ok ?
10
+
9
11
  const isNilOrEmpty = R.either(R.isNil, R.isEmpty);
10
12
  const isNilOrEmptyArray = el => {
11
13
  if (!Array.isArray(el)) return true;
12
14
  return R.isNil(el) || R.isEmpty(el);
13
15
  };
14
16
 
15
- const doMap = (obj, map) => {
17
+ const doMap = (obj, map, omitParam = []) => {
16
18
  const retVal = {};
19
+ const omit = [
20
+ ...omitParam,
21
+ ...Object.keys(map),
22
+ ];
17
23
  Object.entries(map).forEach(([attribute, fn]) => {
18
24
  const newVal = fn(obj);
19
25
  if (newVal !== undefined) {
20
26
  retVal[attribute] = newVal;
21
27
  }
22
28
  });
23
- return retVal;
29
+ return {
30
+ ...retVal,
31
+ ...R.omit(omit, obj),
32
+ };
24
33
  };
25
34
 
26
35
  const doMapCurry = mapObj => item => doMap(item, mapObj);
@@ -28,15 +37,18 @@ const doMapCurry = mapObj => item => doMap(item, mapObj);
28
37
  const productMapIn = {
29
38
  productId: R.path(['id']),
30
39
  productName: R.path(['title']),
31
- options: e => e.options.map(option => ({
32
- optionId: R.path(['id'], option),
33
- optionName: R.path(['title'], option),
34
- })),
40
+ options: e => R.pathOr(undefined, ['options'], e).map(option => doMap(option, optionMapIn, ['id', 'title'])),
41
+ };
42
+
43
+ const optionMapIn = {
44
+ optionId: R.path(['id']),
45
+ optionName: R.path(['title']),
46
+ units: option => R.pathOr(undefined, ['units'], option).map(unit => doMap(unit, unitMap)),
35
47
  };
36
48
 
37
49
  const rateMap = {
38
50
  rateId: R.path(['id']),
39
- rateName: R.path(['internalName']),
51
+ rateName: rateName => R.toLower(R.path(['internalName'], rateName)),
40
52
  pricing: R.path(['pricingFrom']),
41
53
  };
42
54
 
@@ -66,12 +78,6 @@ const capitalize = sParam => {
66
78
  return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
67
79
  };
68
80
 
69
- // const optionMap = {
70
- // optionId: R.path(['id']),
71
- // optionName: R.path(['title']),
72
- // units: option => R.pathOr(undefined, ['units'], option).map(unit => doMap(unit, unitMap)),
73
- // };
74
-
75
81
  const unitMap = {
76
82
  unitId: R.path(['id']),
77
83
  unitName: R.path(['title']),
@@ -197,7 +203,7 @@ class Plugin {
197
203
  headers,
198
204
  }));
199
205
  if (!Array.isArray(results)) results = [results];
200
- let products = results.map(e => doMap(e, productMapIn));
206
+ let products = results.map(e => doMap(e, productMapIn, ['id', 'title']));
201
207
  // dynamic extra filtering
202
208
  if (!isNilOrEmpty(payload)) {
203
209
  const extraFilters = R.omit(['productId'], payload);
@@ -245,7 +251,7 @@ class Plugin {
245
251
  url,
246
252
  headers,
247
253
  });
248
- }, { concurrency: 3 /* is this ok ? */ }).map(({ data }) => data);
254
+ }, { concurrency: CONCURRENCY }).map(({ data }) => data);
249
255
  productsDetail = R.indexBy(R.prop('id'), productsDetail);
250
256
  // console.log({ optionIds });
251
257
  productIds.forEach((productId, productIdIx) => {
@@ -324,7 +330,7 @@ class Plugin {
324
330
  data,
325
331
  headers,
326
332
  });
327
- }, { concurrency: 3 }) // is this ok ?
333
+ }, { concurrency: CONCURRENCY })
328
334
  ).map(({ data }) => data);
329
335
  availability = availability.map(
330
336
  (avails, availsIx) => (avails.map(
@@ -345,6 +351,78 @@ class Plugin {
345
351
  return { availability };
346
352
  }
347
353
 
354
+ async availabilityCalendar({
355
+ token: {
356
+ apiKey = this.apiKey,
357
+ endpoint = this.endpoint,
358
+ octoEnv = this.octoEnv,
359
+ acceptLanguage = this.acceptLanguage,
360
+ },
361
+ token,
362
+ payload: {
363
+ productIds,
364
+ optionIds,
365
+ occupancies,
366
+ startDate,
367
+ // endDate,
368
+ dateFormat,
369
+ },
370
+ }) {
371
+ assert(this.jwtKey, 'JWT secret should be set');
372
+ assert(occupancies.length > 0, 'there should be at least one occupancy');
373
+ assert(
374
+ productIds.length === optionIds.length,
375
+ 'mismatched productIds/options length',
376
+ );
377
+ assert(
378
+ optionIds.length === occupancies.length,
379
+ 'mismatched options/occupancies length',
380
+ );
381
+ assert(productIds.every(Boolean), 'some invalid productId(s)');
382
+ assert(optionIds.every(Boolean), 'some invalid optionId(s)');
383
+ assert(occupancies.every(Boolean), 'some invalid occupacies(s)');
384
+ const localDateStart = moment(startDate, dateFormat).format('YYYY-MM-DD');
385
+ const localDateEnd = moment(startDate, dateFormat).format('YYYY-MM-DD');
386
+ // obtain the rates
387
+ const { quote } = await this.searchQuote({
388
+ token,
389
+ payload: {
390
+ productIds,
391
+ optionIds,
392
+ occupancies,
393
+ },
394
+ });
395
+ const rates = quote.map(q => q.map(({ rateId }) => rateId));
396
+ const headers = getHeaders({
397
+ apiKey,
398
+ endpoint,
399
+ octoEnv,
400
+ acceptLanguage,
401
+ });
402
+ const url = `${endpoint || this.endpoint}/availability/calendar`;
403
+ const availability = (
404
+ await Promise.map(rates, async (rate, rateIx) => {
405
+ const qtys = R.countBy(x => x)(rate);
406
+ const data = {
407
+ productId: productIds[rateIx],
408
+ optionId: optionIds[rateIx],
409
+ localDateStart,
410
+ localDateEnd,
411
+ units: Object.entries(qtys).map(([id, quantity]) => ({
412
+ id, quantity,
413
+ })),
414
+ };
415
+ return axios({
416
+ method: 'post',
417
+ url,
418
+ data,
419
+ headers,
420
+ });
421
+ }, { concurrency: CONCURRENCY })
422
+ ).map(({ data }) => data);
423
+ return { availability };
424
+ }
425
+
348
426
  async createBooking({
349
427
  token: {
350
428
  apiKey = this.apiKey,
package/index.test.js CHANGED
@@ -10,13 +10,13 @@ const app = new Plugin({
10
10
  jwtKey: process.env.ti2_ventrata_jwtKey,
11
11
  });
12
12
 
13
- const rnd = (arr) => arr[Math.floor(Math.random() * arr.length)];
13
+ const rnd = arr => arr[Math.floor(Math.random() * arr.length)];
14
14
 
15
15
  describe('search tests', () => {
16
16
  let products;
17
17
  let testProduct = {
18
18
  productName: 'Pub Crawl Tour',
19
- }
19
+ };
20
20
  const token = {
21
21
  apiKey: process.env.ti2_ventrata_apiKey,
22
22
  endpoint: process.env.ti2_ventrata_endpoint,
@@ -74,7 +74,7 @@ describe('search tests', () => {
74
74
  token,
75
75
  payload: {
76
76
  productId: testProduct.productId,
77
- }
77
+ },
78
78
  });
79
79
  expect(Array.isArray(retVal.products)).toBeTruthy();
80
80
  expect(retVal.products).toHaveLength(1);
@@ -85,12 +85,35 @@ describe('search tests', () => {
85
85
  token,
86
86
  payload: {
87
87
  productName: '*bus*',
88
- }
88
+ },
89
89
  });
90
90
  expect(Array.isArray(retVal.products)).toBeTruthy();
91
91
  expect(retVal.products.length).toBeGreaterThan(0);
92
92
  busProducts = retVal.products;
93
93
  });
94
+ it('should be able to get an availability calendar', async () => {
95
+ const retVal = await app.availabilityCalendar({
96
+ token,
97
+ payload: {
98
+ startDate: moment().add(6, 'M').format(dateFormat),
99
+ endDate: moment().add(6, 'M').add(2, 'd').format(dateFormat),
100
+ dateFormat,
101
+ productIds: [
102
+ '28ca088b-bc7b-4746-ab06-5971f1ed5a5e',
103
+ '5d981651-e204-4549-bfbe-691043dd2515',
104
+ ],
105
+ optionIds: ['DEFAULT', 'DEFAULT'],
106
+ occupancies: [
107
+ [{ age: 30 }, { age: 40 }],
108
+ [{ age: 30 }, { age: 40 }],
109
+ ],
110
+ },
111
+ });
112
+ expect(retVal).toBeTruthy();
113
+ const { availability } = retVal;
114
+ expect(availability).toHaveLength(2);
115
+ expect(availability[0].length).toBeGreaterThan(0);
116
+ });
94
117
  it('should be able to get quotes', async () => {
95
118
  const retVal = await app.searchQuote({
96
119
  token,
@@ -99,9 +122,8 @@ describe('search tests', () => {
99
122
  endDate: moment().add(6, 'M').add(2, 'd').format(dateFormat),
100
123
  dateFormat,
101
124
  productIds: busProducts.map(({ productId }) => productId),
102
- optionIds: busProducts.map(({ options }) =>
103
- faker.random.arrayElement(options).optionId
104
- ),
125
+ optionIds: busProducts.map(({ options }) =>
126
+ faker.random.arrayElement(options).optionId),
105
127
  occupancies: [
106
128
  [{ age: 30 }, { age: 40 }],
107
129
  [{ age: 30 }, { age: 40 }],
@@ -109,10 +131,10 @@ describe('search tests', () => {
109
131
  },
110
132
  });
111
133
  expect(retVal).toBeTruthy();
112
- ({ quote } = retVal);
134
+ const { quote } = retVal;
113
135
  expect(quote.length).toBeGreaterThan(0);
114
136
  expect(quote[0]).toContainObject([{
115
- rateId: 'adult',
137
+ rateName: 'adult',
116
138
  pricing: expect.toContainObject([{
117
139
  currency: 'USD',
118
140
  }]),
@@ -128,7 +150,7 @@ describe('search tests', () => {
128
150
  dateFormat,
129
151
  productIds: [
130
152
  '28ca088b-bc7b-4746-ab06-5971f1ed5a5e',
131
- '5d981651-e204-4549-bfbe-691043dd2515'
153
+ '5d981651-e204-4549-bfbe-691043dd2515',
132
154
  ],
133
155
  optionIds: ['DEFAULT', 'DEFAULT'],
134
156
  occupancies: [
@@ -138,9 +160,9 @@ describe('search tests', () => {
138
160
  },
139
161
  });
140
162
  expect(retVal).toBeTruthy();
141
- ({ availability } = retVal);
163
+ const { availability } = retVal;
142
164
  expect(availability).toHaveLength(2);
143
- expect(availability[0].length).toBeGreaterThan(0)
165
+ expect(availability[0].length).toBeGreaterThan(0);
144
166
  availabilityKey = R.path([0, 0, 'key'], availability);
145
167
  expect(availabilityKey).toBeTruthy();
146
168
  });
@@ -159,7 +181,7 @@ describe('search tests', () => {
159
181
  phoneNumber: faker.phone.phoneNumber(),
160
182
  emailAddress: `salvador+tests_${faker.lorem.slug()}@tourconnect.com`,
161
183
  country: faker.address.countryCode(),
162
- locales:  ['en-US', 'en', 'es'],
184
+ locales: ['en-US', 'en', 'es'],
163
185
  },
164
186
  reference,
165
187
  },
@@ -167,9 +189,9 @@ describe('search tests', () => {
167
189
  expect(retVal.booking).toBeTruthy();
168
190
  ({ booking } = retVal);
169
191
  expect(booking).toBeTruthy();
170
- expect(R.path(['id'], booking)).toBeTruthy()
171
- expect(R.path(['supplierId'], booking)).toBeTruthy()
172
- expect(R.path(['cancellable'], booking)).toBeTruthy()
192
+ expect(R.path(['id'], booking)).toBeTruthy();
193
+ expect(R.path(['supplierId'], booking)).toBeTruthy();
194
+ expect(R.path(['cancellable'], booking)).toBeTruthy();
173
195
  // console.log({ booking });
174
196
  });
175
197
  it('should be able to cancel the booking', async () => {
@@ -178,13 +200,13 @@ describe('search tests', () => {
178
200
  payload: {
179
201
  bookingId: booking.id,
180
202
  reason: faker.lorem.paragraph(),
181
- }
203
+ },
182
204
  });
183
- ({ cancellation } = retVal);
205
+ const { cancellation } = retVal;
184
206
  expect(cancellation).toBeTruthy();
185
207
  expect(cancellation).toBeTruthy();
186
- expect(R.path(['id'], cancellation)).toBeTruthy()
187
- expect(R.path(['cancellable'], cancellation)).toBeFalsy()
208
+ expect(R.path(['id'], cancellation)).toBeTruthy();
209
+ expect(R.path(['cancellable'], cancellation)).toBeFalsy();
188
210
  });
189
211
  let bookings = [];
190
212
  it('it should be able to search bookings by id', async () => {
@@ -192,33 +214,33 @@ describe('search tests', () => {
192
214
  token,
193
215
  payload: {
194
216
  bookingId: booking.id,
195
- }
217
+ },
196
218
  });
197
219
  expect(Array.isArray(retVal.bookings)).toBeTruthy();
198
220
  ({ bookings } = retVal);
199
- expect(R.path([0, 'id'], bookings)).toBeTruthy()
221
+ expect(R.path([0, 'id'], bookings)).toBeTruthy();
200
222
  });
201
223
  it('it should be able to search bookings by reference', async () => {
202
224
  const retVal = await app.searchBooking({
203
225
  token,
204
226
  payload: {
205
227
  bookingId: reference,
206
- }
228
+ },
207
229
  });
208
230
  expect(Array.isArray(retVal.bookings)).toBeTruthy();
209
231
  ({ bookings } = retVal);
210
- expect(R.path([0, 'id'], bookings)).toBeTruthy()
232
+ expect(R.path([0, 'id'], bookings)).toBeTruthy();
211
233
  });
212
234
  it('it should be able to search bookings by supplierId', async () => {
213
235
  const retVal = await app.searchBooking({
214
236
  token,
215
237
  payload: {
216
238
  bookingId: booking.supplierId,
217
- }
239
+ },
218
240
  });
219
241
  expect(Array.isArray(retVal.bookings)).toBeTruthy();
220
242
  ({ bookings } = retVal);
221
- expect(R.path([0, 'id'], bookings)).toBeTruthy()
243
+ expect(R.path([0, 'id'], bookings)).toBeTruthy();
222
244
  });
223
245
  it('it should be able to search bookings by travelDate', async () => {
224
246
  const retVal = await app.searchBooking({
@@ -227,11 +249,11 @@ describe('search tests', () => {
227
249
  travelDateStart: moment().add(6, 'M').format(dateFormat),
228
250
  travelDateEnd: moment().add(6, 'M').add(2, 'd').format(dateFormat),
229
251
  dateFormat,
230
- }
252
+ },
231
253
  });
232
254
  expect(Array.isArray(retVal.bookings)).toBeTruthy();
233
255
  ({ bookings } = retVal);
234
- expect(R.path([0, 'id'], bookings)).toBeTruthy()
256
+ expect(R.path([0, 'id'], bookings)).toBeTruthy();
235
257
  });
236
- });
258
+ });
237
259
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2-ventrata",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "description": "Ventrata's TI2 Plugin",
5
5
  "main": "index.js",
6
6
  "scripts": {