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.
- package/index.js +94 -16
- package/index.test.js +51 -29
- 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
|
|
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 =>
|
|
32
|
-
|
|
33
|
-
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
134
|
+
const { quote } = retVal;
|
|
113
135
|
expect(quote.length).toBeGreaterThan(0);
|
|
114
136
|
expect(quote[0]).toContainObject([{
|
|
115
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
});
|