@curless/hotel-storefront 0.24.0 → 0.24.1
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/README.md +7 -0
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts +1 -53
- package/dist/__tests__/__fixtures__/huazhu-fake.d.ts.map +1 -1
- package/dist/__tests__/__fixtures__/huazhu-fake.js +2 -339
- package/dist/__tests__/__fixtures__/huazhu-fake.js.map +1 -1
- package/dist/__tests__/huazhu-sandbox.test.d.ts +2 -0
- package/dist/__tests__/huazhu-sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/huazhu-sandbox.test.js +172 -0
- package/dist/__tests__/huazhu-sandbox.test.js.map +1 -0
- package/dist/__tests__/tool-descriptions.test.js +29 -4
- package/dist/__tests__/tool-descriptions.test.js.map +1 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/huazhu/fake.d.ts +64 -0
- package/dist/huazhu/fake.d.ts.map +1 -0
- package/dist/huazhu/fake.js +365 -0
- package/dist/huazhu/fake.js.map +1 -0
- package/dist/huazhu/sandbox.d.ts +26 -0
- package/dist/huazhu/sandbox.d.ts.map +1 -0
- package/dist/huazhu/sandbox.js +289 -0
- package/dist/huazhu/sandbox.js.map +1 -0
- package/dist/legal.d.ts.map +1 -1
- package/dist/legal.js +48 -22
- package/dist/legal.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +5 -0
- package/dist/surface.js.map +1 -1
- package/dist/vocab.d.ts +5 -0
- package/dist/vocab.d.ts.map +1 -1
- package/dist/vocab.js +2 -1
- package/dist/vocab.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,13 @@ Everything goes through one `HotelBackend` interface, and there are two:
|
|
|
60
60
|
Huazhu's rules require. Credentials come from the environment
|
|
61
61
|
(`HUAZHU_CLIENT_ID`, `HUAZHU_CLIENT_SECRET`, `HUAZHU_DISTRIBUTOR_ID`), never
|
|
62
62
|
the config.
|
|
63
|
+
- **`data.api.sandbox: true`** is the same connector against a SIMULATED
|
|
64
|
+
Huazhu, for testing before there is an account: `node dist/huazhu/sandbox.js`
|
|
65
|
+
serves the distributor API as its documentation describes it, with eight
|
|
66
|
+
invented hotels named 【模拟】/[Sandbox] and no photographs, and keeps its
|
|
67
|
+
bookings in `SANDBOX_STATE_FILE`. The flag makes every answer say `demo`, the
|
|
68
|
+
instructions and the legal pages say it is a test environment, and nothing
|
|
69
|
+
claim a booking went to Huazhu - without it an api source is taken to be real.
|
|
63
70
|
|
|
64
71
|
A backend says what it cannot do through `capabilities` — Huazhu has no loyalty
|
|
65
72
|
sign-in, no add-ons and no in-place date change — and the tools, the server
|
|
@@ -1,54 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export type FakeRate = {
|
|
3
|
-
product: Product;
|
|
4
|
-
/** Taxes in, per room-night, for up to `adults` adults. */
|
|
5
|
-
price: (date: string) => number;
|
|
6
|
-
before?: (date: string) => number;
|
|
7
|
-
adults?: number;
|
|
8
|
-
inventory?: (date: string) => number;
|
|
9
|
-
guarantee?: string;
|
|
10
|
-
cancel?: string;
|
|
11
|
-
meal?: {
|
|
12
|
-
type: string;
|
|
13
|
-
count: number;
|
|
14
|
-
};
|
|
15
|
-
restriction?: Partial<Record<keyof DailyOffer['restriction'], (date: string) => unknown>>;
|
|
16
|
-
};
|
|
17
|
-
/** A failure injected on `path`: after `skip` good calls, `times` times (default once). */
|
|
18
|
-
type Fail = {
|
|
19
|
-
path: string;
|
|
20
|
-
kind: 'unreachable' | 'http502' | 'answer-lost' | number;
|
|
21
|
-
times?: number;
|
|
22
|
-
skip?: number;
|
|
23
|
-
};
|
|
24
|
-
export type FakeBooking = {
|
|
25
|
-
resId: string;
|
|
26
|
-
distributorResId: string;
|
|
27
|
-
request: BookRequest;
|
|
28
|
-
status: OrderStatus;
|
|
29
|
-
cancelCode: string;
|
|
30
|
-
};
|
|
31
|
-
export declare const createFakeHuazhu: (opts: {
|
|
32
|
-
hotels: HotelStatic[];
|
|
33
|
-
rates: Record<string, FakeRate[]>;
|
|
34
|
-
now: () => number;
|
|
35
|
-
distributorId?: string;
|
|
36
|
-
clientId?: string;
|
|
37
|
-
secret?: string;
|
|
38
|
-
}) => {
|
|
39
|
-
fetch: typeof fetch;
|
|
40
|
-
calls: {
|
|
41
|
-
path: string;
|
|
42
|
-
body: unknown;
|
|
43
|
-
headers: Record<string, string>;
|
|
44
|
-
}[];
|
|
45
|
-
bookings: Map<string, FakeBooking>;
|
|
46
|
-
fail: (f: Fail) => number;
|
|
47
|
-
retireToken: () => void;
|
|
48
|
-
readonly tokens: number;
|
|
49
|
-
};
|
|
50
|
-
/** One domestic hotel shaped like the documentation's example. */
|
|
51
|
-
export declare const fakeHotel: (id: string, over?: Partial<HotelStatic>) => HotelStatic;
|
|
52
|
-
export declare const product: (roomTypeCode: string, rateCode: string, over?: Partial<Product>) => Product;
|
|
53
|
-
export {};
|
|
1
|
+
export * from '../../huazhu/fake.js';
|
|
54
2
|
//# sourceMappingURL=huazhu-fake.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"huazhu-fake.d.ts","sourceRoot":"","sources":["../../../src/__tests__/__fixtures__/huazhu-fake.ts"],"names":[],"mappings":"AACA,
|
|
1
|
+
{"version":3,"file":"huazhu-fake.d.ts","sourceRoot":"","sources":["../../../src/__tests__/__fixtures__/huazhu-fake.ts"],"names":[],"mappings":"AACA,cAAc,sBAAsB,CAAC"}
|
|
@@ -1,340 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
const distributorId = opts.distributorId ?? 'CURLESS';
|
|
4
|
-
const basic = Buffer.from(`${opts.clientId ?? 'cid'}:${opts.secret ?? 'sec'}`).toString('base64');
|
|
5
|
-
let token = '';
|
|
6
|
-
let tokens = 0;
|
|
7
|
-
const fails = [];
|
|
8
|
-
const bookings = new Map();
|
|
9
|
-
const calls = [];
|
|
10
|
-
const held = new Map();
|
|
11
|
-
let resSeq = 1000;
|
|
12
|
-
const envelope = (code, content, message = code === 1000 ? 'Success' : 'Error', responseDes = '') => new Response(JSON.stringify({ code, ...(content !== undefined ? { content } : {}), message, responseDes }), {
|
|
13
|
-
status: 200,
|
|
14
|
-
headers: { 'content-type': 'application/json' },
|
|
15
|
-
});
|
|
16
|
-
const today = () => new Date(opts.now() + 8 * 3_600_000).toISOString().slice(0, 10);
|
|
17
|
-
const dates = (start, end) => {
|
|
18
|
-
const out = [];
|
|
19
|
-
for (let d = start; d <= end; d = addDays(d, 1))
|
|
20
|
-
out.push(d);
|
|
21
|
-
return out;
|
|
22
|
-
};
|
|
23
|
-
const key = (hotelId, p) => `${hotelId}|${p.roomTypeCode}|${p.rateCode}`;
|
|
24
|
-
const left = (hotelId, r, d) => Math.max(0, (r.inventory?.(d) ?? 5) - (held.get(`${key(hotelId, r.product)}|${d}`) ?? 0));
|
|
25
|
-
const offerOf = (hotelId, r, days) => {
|
|
26
|
-
const restriction = {
|
|
27
|
-
close: days.map((d) => r.restriction?.close?.(d) ?? false),
|
|
28
|
-
minStayArrival: days.map((d) => r.restriction?.minStayArrival?.(d) ?? null),
|
|
29
|
-
maxStayArrival: days.map((d) => r.restriction?.maxStayArrival?.(d) ?? 999),
|
|
30
|
-
minStayThrough: days.map((d) => r.restriction?.minStayThrough?.(d) ?? 0),
|
|
31
|
-
maxStayThrough: days.map((d) => r.restriction?.maxStayThrough?.(d) ?? 0),
|
|
32
|
-
minAdvanceDay: days.map((d) => r.restriction?.minAdvanceDay?.(d) ?? null),
|
|
33
|
-
maxAdvanceDay: days.map((d) => r.restriction?.maxAdvanceDay?.(d) ?? 999),
|
|
34
|
-
cta: days.map((d) => r.restriction?.cta?.(d) ?? false),
|
|
35
|
-
ctd: days.map((d) => r.restriction?.ctd?.(d) ?? false),
|
|
36
|
-
fplos: days.map((d) => r.restriction?.fplos?.(d) ?? null),
|
|
37
|
-
};
|
|
38
|
-
void hotelId;
|
|
39
|
-
return {
|
|
40
|
-
roomTypeCode: r.product.roomTypeCode,
|
|
41
|
-
rateCode: r.product.rateCode,
|
|
42
|
-
currencyCode: 'CNY',
|
|
43
|
-
rates: [
|
|
44
|
-
{
|
|
45
|
-
maxAdultCount: r.adults ?? 2,
|
|
46
|
-
amountAfterTax: days.map((d) => r.price(d)),
|
|
47
|
-
...(r.before ? { amountBeforeTax: days.map((d) => r.before?.(d) ?? 0) } : {}),
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
mealPlanType: r.meal?.type ?? 'RO',
|
|
51
|
-
mealPlanCounts: days.map(() => r.meal?.count ?? 0),
|
|
52
|
-
restriction,
|
|
53
|
-
guaranteePolicies: days.map(() => ({ code: r.guarantee ?? 'PL' })),
|
|
54
|
-
cancelPolicies: days.map(() => ({ code: r.cancel ?? '6PM1D1N_100P' })),
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
const handle = async (url, init) => {
|
|
58
|
-
const path = url.pathname;
|
|
59
|
-
const headers = Object.fromEntries(new Headers(init.headers).entries());
|
|
60
|
-
const body = init.body ? JSON.parse(String(init.body)) : undefined;
|
|
61
|
-
calls.push({ path, body, headers });
|
|
62
|
-
const f = fails.find((x) => x.path === path);
|
|
63
|
-
let passedOver = false;
|
|
64
|
-
if (f && (f.skip ?? 0) > 0) {
|
|
65
|
-
f.skip = (f.skip ?? 0) - 1;
|
|
66
|
-
passedOver = true;
|
|
67
|
-
}
|
|
68
|
-
else if (f && f.kind !== 'answer-lost') {
|
|
69
|
-
f.times = (f.times ?? 1) - 1;
|
|
70
|
-
if (f.times <= 0)
|
|
71
|
-
fails.splice(fails.indexOf(f), 1);
|
|
72
|
-
if (f.kind === 'unreachable')
|
|
73
|
-
throw new TypeError('fetch failed: ECONNRESET');
|
|
74
|
-
if (f.kind === 'http502')
|
|
75
|
-
return new Response('bad gateway', { status: 502 });
|
|
76
|
-
return envelope(f.kind, undefined, 'Injected', 'injected by the test');
|
|
77
|
-
}
|
|
78
|
-
if (path === '/oauth/token') {
|
|
79
|
-
if (headers.authorization !== `Basic ${basic}`)
|
|
80
|
-
return new Response('{"error":"invalid_client"}', { status: 401 });
|
|
81
|
-
tokens += 1;
|
|
82
|
-
token = `tok-${tokens}`;
|
|
83
|
-
return new Response(JSON.stringify({
|
|
84
|
-
access_token: token,
|
|
85
|
-
token_type: 'Bearer',
|
|
86
|
-
expires_in: 3600,
|
|
87
|
-
scope: 'ALL',
|
|
88
|
-
}), { status: 200 });
|
|
89
|
-
}
|
|
90
|
-
// Every business call: the four headers, and the current token.
|
|
91
|
-
if (headers.distributorid !== distributorId)
|
|
92
|
-
return envelope(2003, undefined, 'Invalid distributor ID');
|
|
93
|
-
if (!headers.timestamp || !headers.traceid)
|
|
94
|
-
return envelope(2004, undefined, 'Required field missing', 'timestamp/traceId');
|
|
95
|
-
if (headers.authorization !== `Bearer ${token}`)
|
|
96
|
-
return envelope(2002, undefined, 'Invalid token');
|
|
97
|
-
const hotel = (id) => opts.hotels.find((h) => h.hotelId === id);
|
|
98
|
-
switch (path) {
|
|
99
|
-
case '/hotels':
|
|
100
|
-
return envelope(1000, opts.hotels.map((h) => ({ hotelId: h.hotelId, hotelName: h.hotelName })));
|
|
101
|
-
case '/hotel': {
|
|
102
|
-
const h = hotel(body.hotelId);
|
|
103
|
-
return h ? envelope(1000, h) : envelope(3201, undefined, 'Invalid hotel Id');
|
|
104
|
-
}
|
|
105
|
-
case '/ari/products': {
|
|
106
|
-
if (!hotel(body.hotelId))
|
|
107
|
-
return envelope(4101, undefined, 'Invalid hotel Id');
|
|
108
|
-
return envelope(1000, {
|
|
109
|
-
hotelId: body.hotelId,
|
|
110
|
-
sellDays: 90,
|
|
111
|
-
products: (opts.rates[body.hotelId] ?? []).map((r) => r.product),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
case '/ari/daily/offers':
|
|
115
|
-
case '/ari/daily/inventory': {
|
|
116
|
-
if (!hotel(body.hotelId))
|
|
117
|
-
return envelope(path.endsWith('offers') ? 4201 : 4304, undefined, 'Invalid hotel Id');
|
|
118
|
-
if (!body.checkinType)
|
|
119
|
-
return envelope(2004, undefined, 'Required field missing', 'checkinType');
|
|
120
|
-
if (nights(body.startDate, body.endDate) > 29 || body.endDate < body.startDate)
|
|
121
|
-
return envelope(path.endsWith('offers') ? 4203 : 4301, undefined, 'Start date and end date does not match the rules');
|
|
122
|
-
if (body.startDate < addDays(today(), -1))
|
|
123
|
-
return envelope(path.endsWith('offers') ? 4205 : 4303, undefined, 'Start date is invalid');
|
|
124
|
-
const days = dates(body.startDate, body.endDate);
|
|
125
|
-
const rates = opts.rates[body.hotelId] ?? [];
|
|
126
|
-
if (path.endsWith('offers')) {
|
|
127
|
-
return envelope(1000, {
|
|
128
|
-
hotelId: body.hotelId,
|
|
129
|
-
startDate: body.startDate,
|
|
130
|
-
endDate: body.endDate,
|
|
131
|
-
dailyOffers: rates.map((r) => offerOf(body.hotelId, r, days)),
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
return envelope(1000, {
|
|
135
|
-
hotelId: body.hotelId,
|
|
136
|
-
startDate: body.startDate,
|
|
137
|
-
endDate: body.endDate,
|
|
138
|
-
dailyInventories: rates.map((r) => ({
|
|
139
|
-
roomTypeCode: r.product.roomTypeCode,
|
|
140
|
-
rateCode: r.product.rateCode,
|
|
141
|
-
inventories: days.map((d) => left(body.hotelId, r, d)),
|
|
142
|
-
})),
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
case '/reservation/prebook':
|
|
146
|
-
case '/reservation/book': {
|
|
147
|
-
const req = body;
|
|
148
|
-
const booking = path.endsWith('book') && !path.endsWith('prebook');
|
|
149
|
-
if (booking) {
|
|
150
|
-
if (!req.distributorResId)
|
|
151
|
-
return envelope(2004, undefined, 'Required field missing', 'distributorResId');
|
|
152
|
-
const had = [...bookings.values()].find((b) => b.distributorResId === req.distributorResId);
|
|
153
|
-
if (had)
|
|
154
|
-
return envelope(5218, undefined, 'DistributorResId already exist');
|
|
155
|
-
if (!req.contactPerson?.lastName || req.contactPerson.lastName.length > 16)
|
|
156
|
-
return envelope(2005, undefined, 'Invalid field', 'contactPerson.lastName');
|
|
157
|
-
}
|
|
158
|
-
const line = req.reservations?.[0];
|
|
159
|
-
if (!line)
|
|
160
|
-
return envelope(2004, undefined, 'Required field missing', 'reservations');
|
|
161
|
-
const r = (opts.rates[req.hotelId] ?? []).find((x) => x.product.roomTypeCode === line.roomTypeCode && x.product.rateCode === line.rateCode);
|
|
162
|
-
if (!r)
|
|
163
|
-
return envelope(booking ? 5236 : 5123, undefined, 'No match found');
|
|
164
|
-
const nightsOf = dates(line.checkin, addDays(line.checkout, -1));
|
|
165
|
-
const want = nightsOf.map((d) => r.price(d));
|
|
166
|
-
const priceOk = want.length === line.amountAfterTax.length &&
|
|
167
|
-
want.every((x, i) => x === line.amountAfterTax[i]);
|
|
168
|
-
const ari = {
|
|
169
|
-
checkin: line.checkin,
|
|
170
|
-
checkout: line.checkout,
|
|
171
|
-
...offerOf(req.hotelId, r, nightsOf),
|
|
172
|
-
inventories: nightsOf.map((d) => left(req.hotelId, r, d)),
|
|
173
|
-
};
|
|
174
|
-
if (!priceOk)
|
|
175
|
-
return envelope(booking ? 5209 : 5108, { hotelId: req.hotelId, reservationsAri: [ari] }, 'Price incorrect for room');
|
|
176
|
-
if (nightsOf.some((d) => left(req.hotelId, r, d) < line.roomCount))
|
|
177
|
-
return envelope(booking ? 5210 : 5109, { hotelId: req.hotelId, reservationsAri: [ari] }, 'No rooms available for requested dates');
|
|
178
|
-
const guarantee = r.guarantee ?? 'PL';
|
|
179
|
-
const allowed = {
|
|
180
|
-
PL: ['NGPL', 'OGPL', 'CCPL'],
|
|
181
|
-
GTX: ['OGPL', 'OPN', 'CCPL', 'CPN'],
|
|
182
|
-
PN: ['OPN', 'CCPN', 'CPN'],
|
|
183
|
-
};
|
|
184
|
-
if (!(allowed[guarantee] ?? []).includes(req.paymentType))
|
|
185
|
-
return envelope(booking ? 5211 : 5114, undefined, 'Payment type invalid');
|
|
186
|
-
if (!booking)
|
|
187
|
-
return envelope(1000, {
|
|
188
|
-
hotelId: req.hotelId,
|
|
189
|
-
preBookToken: `PB${calls.length}`,
|
|
190
|
-
reservationsAri: [ari],
|
|
191
|
-
});
|
|
192
|
-
resSeq += 1;
|
|
193
|
-
const resId = `R${req.hotelId}${resSeq}`;
|
|
194
|
-
for (const d of nightsOf) {
|
|
195
|
-
const k = `${key(req.hotelId, r.product)}|${d}`;
|
|
196
|
-
held.set(k, (held.get(k) ?? 0) + line.roomCount);
|
|
197
|
-
}
|
|
198
|
-
bookings.set(resId, {
|
|
199
|
-
resId,
|
|
200
|
-
distributorResId: req.distributorResId,
|
|
201
|
-
request: req,
|
|
202
|
-
status: 'HOTEL_RESERVATION',
|
|
203
|
-
cancelCode: r.cancel ?? '6PM1D1N_100P',
|
|
204
|
-
});
|
|
205
|
-
const lost = passedOver
|
|
206
|
-
? undefined
|
|
207
|
-
: fails.find((x) => x.path === path && x.kind === 'answer-lost');
|
|
208
|
-
if (lost) {
|
|
209
|
-
fails.splice(fails.indexOf(lost), 1);
|
|
210
|
-
throw new TypeError('fetch failed: read timeout');
|
|
211
|
-
}
|
|
212
|
-
return envelope(1000, { resId, resCode: `RC${resSeq}` });
|
|
213
|
-
}
|
|
214
|
-
case '/reservation/detail': {
|
|
215
|
-
const b = bookings.get(body.resId);
|
|
216
|
-
if (!b)
|
|
217
|
-
return envelope(5302, undefined, 'Reservation not found');
|
|
218
|
-
const line = b.request.reservations[0];
|
|
219
|
-
return envelope(1000, {
|
|
220
|
-
hotelId: b.request.hotelId,
|
|
221
|
-
checkInType: 'DAY',
|
|
222
|
-
paymentType: b.request.paymentType,
|
|
223
|
-
distributorResId: b.distributorResId,
|
|
224
|
-
resId: b.resId,
|
|
225
|
-
status: b.status,
|
|
226
|
-
bookingTime: opts.now(),
|
|
227
|
-
reservations: [
|
|
228
|
-
{
|
|
229
|
-
...line,
|
|
230
|
-
cancelPolicyCode: b.cancelCode,
|
|
231
|
-
estimatedArrivalTime: line.estimatedArrivalTime ?? '12:00',
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
case '/reservation/cancel': {
|
|
237
|
-
const b = bookings.get(body.resId);
|
|
238
|
-
if (!b)
|
|
239
|
-
return envelope(5403, undefined, 'Reservation not found');
|
|
240
|
-
if (b.status === 'HOTEL_CANCEL')
|
|
241
|
-
return envelope(5402, undefined, 'Reservation already cancelled');
|
|
242
|
-
b.status = 'HOTEL_CANCEL';
|
|
243
|
-
const line = b.request.reservations[0];
|
|
244
|
-
for (const d of dates(line.checkin, addDays(line.checkout, -1))) {
|
|
245
|
-
const k = `${b.request.hotelId}|${line.roomTypeCode}|${line.rateCode}|${d}`;
|
|
246
|
-
held.set(k, Math.max(0, (held.get(k) ?? 0) - line.roomCount));
|
|
247
|
-
}
|
|
248
|
-
return envelope(1000, { penaltyAmount: 0, penaltyCurrencyCode: 'CNY' });
|
|
249
|
-
}
|
|
250
|
-
case '/reservation/consistency/query': {
|
|
251
|
-
const b = [...bookings.values()].find((x) => x.distributorResId === body.distributorResId);
|
|
252
|
-
return b
|
|
253
|
-
? envelope(1000, { resId: b.resId, status: b.status })
|
|
254
|
-
: envelope(5602, undefined, 'Reservation not found');
|
|
255
|
-
}
|
|
256
|
-
default:
|
|
257
|
-
return new Response('not found', { status: 404 });
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
return {
|
|
261
|
-
fetch: (async (input, init) => handle(new URL(typeof input === 'string' ? input : input instanceof URL ? input.href : input.url), init ?? {})),
|
|
262
|
-
calls,
|
|
263
|
-
bookings,
|
|
264
|
-
fail: (f) => fails.push(f),
|
|
265
|
-
retireToken: () => {
|
|
266
|
-
token = `retired-${token}`;
|
|
267
|
-
},
|
|
268
|
-
get tokens() {
|
|
269
|
-
return tokens;
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
};
|
|
273
|
-
/** One domestic hotel shaped like the documentation's example. */
|
|
274
|
-
export const fakeHotel = (id, over = {}) => ({
|
|
275
|
-
hotelId: id,
|
|
276
|
-
hotelName: `汉庭酒店(上海人民广场${id}店)`,
|
|
277
|
-
hotelNameEn: `Hanting Hotel Shanghai People's Square ${id}`,
|
|
278
|
-
brandCode: 2,
|
|
279
|
-
brandName: '汉庭',
|
|
280
|
-
provinceNo: '310000',
|
|
281
|
-
provinceName: '上海市',
|
|
282
|
-
cityNo: '310100',
|
|
283
|
-
cityName: '上海市',
|
|
284
|
-
countyNo: '310101',
|
|
285
|
-
countyName: '黄浦区',
|
|
286
|
-
hotelAddress: '上海市黄浦区延安东路1号',
|
|
287
|
-
hotelAddressEn: "1 Yan'an East Road, Huangpu District, Shanghai",
|
|
288
|
-
telephone: '63200000',
|
|
289
|
-
hotelWordIntroduce: '近人民广场地铁站,步行可达南京路步行街。',
|
|
290
|
-
international: false,
|
|
291
|
-
tradeCurrency: 'CNY',
|
|
292
|
-
hotelRoomTypeList: [
|
|
293
|
-
{
|
|
294
|
-
roomTypeCode: 'DR1',
|
|
295
|
-
roomTypeName: '高级大床房',
|
|
296
|
-
hotelArea: 20,
|
|
297
|
-
isHasWindow: '1',
|
|
298
|
-
maxOccupancy: 999,
|
|
299
|
-
status: 1,
|
|
300
|
-
bedTypeList: [{ bedTypeCode: 'A', bedSpecNo: '-1', bedTypeName: '大床', bedSpec: '1.8m' }],
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
roomTypeCode: 'TR1',
|
|
304
|
-
roomTypeName: '高级双床房',
|
|
305
|
-
hotelArea: 22,
|
|
306
|
-
isHasWindow: '3',
|
|
307
|
-
maxOccupancy: 2,
|
|
308
|
-
status: 1,
|
|
309
|
-
bedTypeList: [{ bedTypeCode: 'B', bedSpecNo: '-1', bedTypeName: '双床', bedSpec: '1.2m' }],
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
hotelServiceList: [
|
|
313
|
-
{ childClassCode: 71, childClassName: '健身房' },
|
|
314
|
-
{ childClassCode: 49, childClassName: '中餐厅' },
|
|
315
|
-
],
|
|
316
|
-
hotelPhotoList: [
|
|
317
|
-
{ typeCode: 'ImagePhoto', fileUrl: 'http://ws-p.example.com/f/a.jpg', sortId: 20, status: 1 },
|
|
318
|
-
{
|
|
319
|
-
typeCode: 'RoomType',
|
|
320
|
-
typeMappingCode: 'DR1',
|
|
321
|
-
fileUrl: 'http://ws-p.example.com/f/dr1.jpg',
|
|
322
|
-
sortId: 10,
|
|
323
|
-
status: 1,
|
|
324
|
-
},
|
|
325
|
-
],
|
|
326
|
-
hotelTrafficList: [
|
|
327
|
-
{ destinationType: '9', destinationName: '人民广场', destinationDistance: '300m' },
|
|
328
|
-
],
|
|
329
|
-
...over,
|
|
330
|
-
});
|
|
331
|
-
export const product = (roomTypeCode, rateCode, over = {}) => ({
|
|
332
|
-
roomTypeCode,
|
|
333
|
-
roomTypeName: roomTypeCode === 'DR1' ? '高级大床房' : '高级双床房',
|
|
334
|
-
rateCode,
|
|
335
|
-
rateCodeName: rateCode,
|
|
336
|
-
checkinType: 'DAY',
|
|
337
|
-
promotion: false,
|
|
338
|
-
...over,
|
|
339
|
-
});
|
|
1
|
+
// The fake moved beside the code it stands in for, so the sandbox can serve it.
|
|
2
|
+
export * from '../../huazhu/fake.js';
|
|
340
3
|
//# sourceMappingURL=huazhu-fake.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"huazhu-fake.js","sourceRoot":"","sources":["../../../src/__tests__/__fixtures__/huazhu-fake.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"huazhu-fake.js","sourceRoot":"","sources":["../../../src/__tests__/__fixtures__/huazhu-fake.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"huazhu-sandbox.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/huazhu-sandbox.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { mkdtempSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { createFakeHuazhu, fakeHotel, product } from '../huazhu/fake.js';
|
|
7
|
+
import { SANDBOX_HOTELS, startSandbox } from '../huazhu/sandbox.js';
|
|
8
|
+
import { buildServer, loadConfig } from '../server.js';
|
|
9
|
+
// The simulated Huazhu a deployment runs against before there is an account:
|
|
10
|
+
// the connector over real HTTP, and every page and answer saying it is a test.
|
|
11
|
+
const { config, configPath } = loadConfig(fileURLToPath(new URL('./__fixtures__/config.json', import.meta.url)));
|
|
12
|
+
const CREDS = {
|
|
13
|
+
HUAZHU_CLIENT_ID: 'sandbox-client',
|
|
14
|
+
HUAZHU_CLIENT_SECRET: 'sandbox-secret',
|
|
15
|
+
HUAZHU_DISTRIBUTOR_ID: 'CURLESS',
|
|
16
|
+
};
|
|
17
|
+
// 10:00 in Shanghai on 1 October 2026.
|
|
18
|
+
const T0 = Date.parse('2026-10-01T02:00:00Z');
|
|
19
|
+
let closers = [];
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
for (const c of closers)
|
|
22
|
+
await c();
|
|
23
|
+
closers = [];
|
|
24
|
+
});
|
|
25
|
+
const sandbox = async (o) => {
|
|
26
|
+
const s = await startSandbox({
|
|
27
|
+
host: '127.0.0.1',
|
|
28
|
+
clientId: CREDS.HUAZHU_CLIENT_ID,
|
|
29
|
+
secret: CREDS.HUAZHU_CLIENT_SECRET,
|
|
30
|
+
distributorId: CREDS.HUAZHU_DISTRIBUTOR_ID,
|
|
31
|
+
...o,
|
|
32
|
+
});
|
|
33
|
+
closers.push(s.close);
|
|
34
|
+
return s;
|
|
35
|
+
};
|
|
36
|
+
const deploy = async (url, now, stateDir = mkdtempSync(join(tmpdir(), 'hz-sb-'))) => {
|
|
37
|
+
const built = buildServer({
|
|
38
|
+
config: {
|
|
39
|
+
...config,
|
|
40
|
+
brand: 'Huazhu',
|
|
41
|
+
naming: { prefix: 'huazhu' },
|
|
42
|
+
data: {
|
|
43
|
+
source: 'api',
|
|
44
|
+
api: { provider: 'huazhu', baseUrl: url, authUrl: url, stateDir, sandbox: true },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
configPath,
|
|
48
|
+
env: CREDS,
|
|
49
|
+
now,
|
|
50
|
+
background: false,
|
|
51
|
+
});
|
|
52
|
+
await built.ready();
|
|
53
|
+
const rpc = async (method, params, session = 'conv-sb') => {
|
|
54
|
+
const res = await built.app.request('/mcp', {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'content-type': 'application/json', 'mcp-session-id': session },
|
|
57
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
|
|
58
|
+
});
|
|
59
|
+
const text = await res.text();
|
|
60
|
+
// biome-ignore lint/suspicious/noExplicitAny: a JSON-RPC answer, read by its shape
|
|
61
|
+
return (text ? JSON.parse(text) : null);
|
|
62
|
+
};
|
|
63
|
+
const call = (name, args = {}) => rpc('tools/call', { name, arguments: args });
|
|
64
|
+
return { app: built.app, rpc, call, stateDir };
|
|
65
|
+
};
|
|
66
|
+
describe('the Huazhu sandbox', () => {
|
|
67
|
+
it('serves only invented hotels, named as such, over HTTP', async () => {
|
|
68
|
+
const now = () => T0;
|
|
69
|
+
const s = await sandbox({ now });
|
|
70
|
+
const d = await deploy(`http://127.0.0.1:${s.port}`, now);
|
|
71
|
+
const list = await d.call('list_huazhu_hotels', { city: '上海' });
|
|
72
|
+
const names = list.result.structuredContent.hotels.map((h) => h.name);
|
|
73
|
+
expect(names.length).toBeGreaterThan(0);
|
|
74
|
+
for (const n of names)
|
|
75
|
+
expect(n).toMatch(/^【模拟】/);
|
|
76
|
+
for (const h of SANDBOX_HOTELS) {
|
|
77
|
+
expect(h.hotelName).toMatch(/^【模拟】/);
|
|
78
|
+
expect(h.hotelNameEn).toMatch(/^\[Sandbox\]/);
|
|
79
|
+
expect(h.hotelPhotoList).toEqual([]);
|
|
80
|
+
}
|
|
81
|
+
const health = await (await fetch(`http://127.0.0.1:${s.port}/healthz`)).json();
|
|
82
|
+
expect(health).toMatchObject({ ok: true, sandbox: true });
|
|
83
|
+
});
|
|
84
|
+
it('says everywhere that it is a test, never that a booking went to Huazhu', async () => {
|
|
85
|
+
const now = () => T0;
|
|
86
|
+
const s = await sandbox({ now });
|
|
87
|
+
const d = await deploy(`http://127.0.0.1:${s.port}`, now);
|
|
88
|
+
const init = await d.rpc('initialize', {});
|
|
89
|
+
expect(init.result.instructions).toMatch(/THIS IS A TEST ENVIRONMENT/);
|
|
90
|
+
expect(init.result.instructions).toMatch(/Demonstration data/);
|
|
91
|
+
const tools = (await d.rpc('tools/list')).result.tools;
|
|
92
|
+
for (const t of tools)
|
|
93
|
+
expect(t.annotations.openWorldHint).toBe(false);
|
|
94
|
+
const health = await (await d.app.request('/healthz')).json();
|
|
95
|
+
expect(health).toMatchObject({ demo: true });
|
|
96
|
+
const privacy = await (await d.app.request('/privacy')).text();
|
|
97
|
+
expect(privacy).toMatch(/This is a test environment/);
|
|
98
|
+
expect(privacy).toMatch(/simulated copy of Huazhu's system|simulated copy of Huazhu's system/);
|
|
99
|
+
expect(privacy).not.toMatch(/who make and keep the reservation/);
|
|
100
|
+
const terms = await (await d.app.request('/terms')).text();
|
|
101
|
+
expect(terms).toMatch(/reserves no room, and nothing is paid/);
|
|
102
|
+
expect(terms).not.toMatch(/own reservation, made in your name/);
|
|
103
|
+
expect(terms).not.toMatch(/sells Huazhu/);
|
|
104
|
+
const docs = await (await d.app.request('/docs')).text();
|
|
105
|
+
expect(docs).toMatch(/a reservation in the simulated system/);
|
|
106
|
+
});
|
|
107
|
+
it("books, keeps the booking across its own restart, and cancels at the rate's fee", async () => {
|
|
108
|
+
let t = T0;
|
|
109
|
+
const now = () => t;
|
|
110
|
+
const stateFile = join(mkdtempSync(join(tmpdir(), 'hz-sb-state-')), 'sandbox.json');
|
|
111
|
+
const first = await sandbox({ now, stateFile });
|
|
112
|
+
const d = await deploy(`http://127.0.0.1:${first.port}`, now);
|
|
113
|
+
const stay = { checkIn: '2026-10-10', checkOut: '2026-10-11', guests: 2 };
|
|
114
|
+
await d.call('add_to_huazhu_trip', { hotel: '1001', room: 'DR1~BAR', ...stay });
|
|
115
|
+
const booked = await d.call('request_huazhu_booking', {
|
|
116
|
+
name: 'Wei Zhang',
|
|
117
|
+
email: 'wei@example.com',
|
|
118
|
+
});
|
|
119
|
+
const text = booked.result.content[0].text;
|
|
120
|
+
const code = text.match(/\*\*(R1001\d+)\*\*/)?.[1];
|
|
121
|
+
expect(code, text).toBeTruthy();
|
|
122
|
+
expect(booked.result.structuredContent.demo).toBe(true);
|
|
123
|
+
// The sandbox restarts on the same port; the booking and its held room survive.
|
|
124
|
+
await first.close();
|
|
125
|
+
closers = closers.filter((c) => c !== first.close);
|
|
126
|
+
await sandbox({ now, stateFile, port: first.port });
|
|
127
|
+
// 20:00 in Shanghai the evening before: past the rate's 18:00 free window.
|
|
128
|
+
t = Date.parse('2026-10-09T12:00:00Z');
|
|
129
|
+
const preview = await d.call('cancel_huazhu_booking', { booking: code });
|
|
130
|
+
expect(preview.result.structuredContent.view).toBe('cancellation-preview');
|
|
131
|
+
const fee = preview.result.structuredContent.fee;
|
|
132
|
+
// The first night is a Saturday, at the weekend rate: 289 x 1.15.
|
|
133
|
+
expect(fee).toMatch(/332/);
|
|
134
|
+
const done = await d.call('cancel_huazhu_booking', { booking: code, confirmed: true });
|
|
135
|
+
const said = done.result.content[0].text;
|
|
136
|
+
expect(said, said).toMatch(/332/);
|
|
137
|
+
expect(said).not.toMatch(/No fee/i);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('the fake Huazhu', () => {
|
|
141
|
+
const hotel = fakeHotel('1001');
|
|
142
|
+
const rates = { '1001': [{ product: product('DR1', 'BAR'), price: () => 328 }] };
|
|
143
|
+
const auth = `Basic ${Buffer.from('cid:sec').toString('base64')}`;
|
|
144
|
+
const token = async (f) => (await (await f.fetch('https://auth/oauth/token', {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { authorization: auth },
|
|
147
|
+
})).json()).access_token;
|
|
148
|
+
const hotels = (f, tok) => f
|
|
149
|
+
.fetch('https://biz/hotels', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: {
|
|
152
|
+
authorization: `Bearer ${tok}`,
|
|
153
|
+
distributorid: 'CURLESS',
|
|
154
|
+
timestamp: '1',
|
|
155
|
+
traceid: 't',
|
|
156
|
+
},
|
|
157
|
+
body: '{}',
|
|
158
|
+
})
|
|
159
|
+
.then((r) => r.json());
|
|
160
|
+
it('keeps the previous token working for 120 seconds, as Huazhu does', async () => {
|
|
161
|
+
let t = T0;
|
|
162
|
+
const f = createFakeHuazhu({ hotels: [hotel], rates, now: () => t });
|
|
163
|
+
const a = await token(f);
|
|
164
|
+
const b = await token(f);
|
|
165
|
+
expect((await hotels(f, b)).code).toBe(1000);
|
|
166
|
+
expect((await hotels(f, a)).code).toBe(1000);
|
|
167
|
+
t += 121_000;
|
|
168
|
+
expect((await hotels(f, a)).code).toBe(2002);
|
|
169
|
+
expect((await hotels(f, b)).code).toBe(1000);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=huazhu-sandbox.test.js.map
|