ti2-tourplan 1.0.133 → 1.0.135
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/__snapshots__/index.test.js.snap +1 -0
- package/index.test.js +45 -0
- package/itinerary-products-search.js +3 -2
- package/option-metadata.js +44 -0
- package/option-metadata.test.js +42 -0
- package/package.json +1 -1
package/index.test.js
CHANGED
|
@@ -867,6 +867,7 @@ describe('search tests', () => {
|
|
|
867
867
|
},
|
|
868
868
|
});
|
|
869
869
|
expect(R.path(['products', 0, 'options', 0, 'city'], retVal)).toBe('London');
|
|
870
|
+
expect(R.path(['products', 0, 'options', 0, 'inclusions'], retVal)).toEqual([]);
|
|
870
871
|
expect(retVal).toMatchSnapshot();
|
|
871
872
|
});
|
|
872
873
|
|
|
@@ -1850,6 +1851,50 @@ describe('search tests', () => {
|
|
|
1850
1851
|
}
|
|
1851
1852
|
});
|
|
1852
1853
|
|
|
1854
|
+
it('searchItineraries searches alphanumeric bookingId candidates as AgentRef', async () => {
|
|
1855
|
+
axios.mockImplementation(getFixture);
|
|
1856
|
+
mockCallTourplan.mockImplementation(async request => {
|
|
1857
|
+
const { model } = request;
|
|
1858
|
+
const listRequest = model && model.ListBookingsRequest;
|
|
1859
|
+
if (listRequest && listRequest.BookingId === 'SGMX269690') {
|
|
1860
|
+
return { ListBookingsReply: { BookingHeaders: { BookingHeader: [] } } };
|
|
1861
|
+
}
|
|
1862
|
+
if (listRequest && listRequest.Ref === 'SGMX269690') {
|
|
1863
|
+
return { ListBookingsReply: { BookingHeaders: { BookingHeader: [] } } };
|
|
1864
|
+
}
|
|
1865
|
+
if (listRequest && listRequest.AgentRef === 'SGMX269690') {
|
|
1866
|
+
return {
|
|
1867
|
+
ListBookingsReply: {
|
|
1868
|
+
BookingHeaders: {
|
|
1869
|
+
BookingHeader: [{ BookingId: '316559' }],
|
|
1870
|
+
},
|
|
1871
|
+
},
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
return defaultCallTourplanImplementation(request);
|
|
1875
|
+
});
|
|
1876
|
+
|
|
1877
|
+
const retVal = await app.searchItineraries({
|
|
1878
|
+
axios,
|
|
1879
|
+
token,
|
|
1880
|
+
typeDefsAndQueries,
|
|
1881
|
+
payload: {
|
|
1882
|
+
bookingId: 'SGMX269690',
|
|
1883
|
+
},
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
expect(retVal.bookings.length).toBe(1);
|
|
1887
|
+
expect(retVal.bookings[0].bookingId).toBe('316559');
|
|
1888
|
+
const listRequests = mockCallTourplan.mock.calls
|
|
1889
|
+
.map(([{ model }]) => model && model.ListBookingsRequest)
|
|
1890
|
+
.filter(Boolean);
|
|
1891
|
+
expect(listRequests).toEqual(expect.arrayContaining([
|
|
1892
|
+
expect.objectContaining({ BookingId: 'SGMX269690' }),
|
|
1893
|
+
expect.objectContaining({ Ref: 'SGMX269690' }),
|
|
1894
|
+
expect.objectContaining({ AgentRef: 'SGMX269690' }),
|
|
1895
|
+
]));
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1853
1898
|
it('searchItineraries by name (NameContains)', async () => {
|
|
1854
1899
|
axios.mockImplementation(getFixture);
|
|
1855
1900
|
const retVal = await app.searchItineraries({
|
|
@@ -7,6 +7,7 @@ const { getCachedLocations } = require('./tp-helpers/locations');
|
|
|
7
7
|
const { getCachedServices } = require('./tp-helpers/services');
|
|
8
8
|
const { getCachedDestinationCountries } = require('./tp-helpers/system-settings');
|
|
9
9
|
const { enrichOptionWithCodeTables } = require('./tp-helpers/option-enrichment');
|
|
10
|
+
const { addStructuredOptionMetadata } = require('./option-metadata');
|
|
10
11
|
const {
|
|
11
12
|
isEnabled,
|
|
12
13
|
normalizePickupPoints,
|
|
@@ -260,7 +261,7 @@ const searchProductsForItinerary = async ({
|
|
|
260
261
|
|| R.path(['PickupPoints'], rawOption),
|
|
261
262
|
)
|
|
262
263
|
: undefined;
|
|
263
|
-
return {
|
|
264
|
+
return addStructuredOptionMetadata({
|
|
264
265
|
...R.omit(['city', 'country', 'rateContext'], currentOption),
|
|
265
266
|
...(city ? { city } : {}),
|
|
266
267
|
...(country ? { country } : {}),
|
|
@@ -269,7 +270,7 @@ const searchProductsForItinerary = async ({
|
|
|
269
270
|
...(R.path([currentOption.optionId], optionRatesByOptionId)
|
|
270
271
|
? { optRates: R.path([currentOption.optionId], optionRatesByOptionId) }
|
|
271
272
|
: {}),
|
|
272
|
-
};
|
|
273
|
+
});
|
|
273
274
|
}),
|
|
274
275
|
}));
|
|
275
276
|
return {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const BREAKFAST = 'breakfast';
|
|
2
|
+
|
|
3
|
+
const normalizeText = value => String(value || '').toLowerCase();
|
|
4
|
+
|
|
5
|
+
const hasNegatedBreakfast = value => {
|
|
6
|
+
const text = normalizeText(value);
|
|
7
|
+
return /\b(?:no|without|excluding|exclude|excludes)\s+breakfast\b/.test(text)
|
|
8
|
+
|| /\b(?:no|without)\s+bed\s+(?:and|&)\s+breakfast\b/.test(text)
|
|
9
|
+
|| /\b(?:do|does)\s+not\s+(?:include|includes)\s+breakfast\b/.test(text)
|
|
10
|
+
|| /\bnot\s+including\s+breakfast\b/.test(text)
|
|
11
|
+
|| /\bbreakfast\b\s+(?:is\s+)?(?:not\s+included|not\s+inclusive|excluded)\b/.test(text);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const includesBreakfast = value => {
|
|
15
|
+
if (hasNegatedBreakfast(value)) return false;
|
|
16
|
+
const text = normalizeText(value);
|
|
17
|
+
return /\bbreakfast\b\s+(?:is\s+)?(?:included|inclusive)\b/.test(text)
|
|
18
|
+
|| /\b(?:including|includes|include|with)\s+breakfast\b/.test(text)
|
|
19
|
+
|| /\bbed\s+(?:and|&)\s+breakfast\b/.test(text);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getOptionInclusions = optionName => (
|
|
23
|
+
includesBreakfast(optionName) ? [BREAKFAST] : []
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const getExtraCategory = extraName => {
|
|
27
|
+
if (hasNegatedBreakfast(extraName)) return null;
|
|
28
|
+
return /\bbreakfast\b/.test(normalizeText(extraName)) ? BREAKFAST : null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const addStructuredOptionMetadata = option => ({
|
|
32
|
+
...option,
|
|
33
|
+
inclusions: getOptionInclusions(option && option.optionName),
|
|
34
|
+
extras: (option && option.extras || []).map(extra => {
|
|
35
|
+
const category = getExtraCategory(extra && extra.name);
|
|
36
|
+
return category ? { ...extra, category } : extra;
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
addStructuredOptionMetadata,
|
|
42
|
+
getExtraCategory,
|
|
43
|
+
getOptionInclusions,
|
|
44
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const {
|
|
2
|
+
addStructuredOptionMetadata,
|
|
3
|
+
getExtraCategory,
|
|
4
|
+
getOptionInclusions,
|
|
5
|
+
} = require('./option-metadata');
|
|
6
|
+
|
|
7
|
+
describe('structured option metadata', () => {
|
|
8
|
+
test.each([
|
|
9
|
+
['Witherview Super King, Including Breakfast', ['breakfast']],
|
|
10
|
+
['Bed and Breakfast', ['breakfast']],
|
|
11
|
+
['Bed & Breakfast', ['breakfast']],
|
|
12
|
+
['Breakfast not included', []],
|
|
13
|
+
['No breakfast included', []],
|
|
14
|
+
['Does not include breakfast', []],
|
|
15
|
+
['Not including breakfast', []],
|
|
16
|
+
['No bed and breakfast', []],
|
|
17
|
+
['Room only', []],
|
|
18
|
+
])('normalizes option inclusion text %s', (optionName, expected) => {
|
|
19
|
+
expect(getOptionInclusions(optionName)).toEqual(expected);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('classifies selectable breakfast extras without changing unrelated extras', () => {
|
|
23
|
+
expect(addStructuredOptionMetadata({
|
|
24
|
+
optionName: 'Superior Room',
|
|
25
|
+
extras: [
|
|
26
|
+
{ id: '1', name: 'Breakfast' },
|
|
27
|
+
{ id: '2', name: 'Airport transfer' },
|
|
28
|
+
],
|
|
29
|
+
})).toEqual({
|
|
30
|
+
optionName: 'Superior Room',
|
|
31
|
+
inclusions: [],
|
|
32
|
+
extras: [
|
|
33
|
+
{ id: '1', name: 'Breakfast', category: 'breakfast' },
|
|
34
|
+
{ id: '2', name: 'Airport transfer' },
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not classify negated breakfast text as a selectable category', () => {
|
|
40
|
+
expect(getExtraCategory('No breakfast')).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|