@curless/hotel-storefront 0.1.0

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 (98) hide show
  1. package/README.md +63 -0
  2. package/dist/__tests__/card-contract.test.d.ts +2 -0
  3. package/dist/__tests__/card-contract.test.d.ts.map +1 -0
  4. package/dist/__tests__/card-contract.test.js +64 -0
  5. package/dist/__tests__/card-contract.test.js.map +1 -0
  6. package/dist/__tests__/data.test.d.ts +2 -0
  7. package/dist/__tests__/data.test.d.ts.map +1 -0
  8. package/dist/__tests__/data.test.js +89 -0
  9. package/dist/__tests__/data.test.js.map +1 -0
  10. package/dist/__tests__/dates.test.d.ts +2 -0
  11. package/dist/__tests__/dates.test.d.ts.map +1 -0
  12. package/dist/__tests__/dates.test.js +71 -0
  13. package/dist/__tests__/dates.test.js.map +1 -0
  14. package/dist/__tests__/demo-hotels.test.d.ts +2 -0
  15. package/dist/__tests__/demo-hotels.test.d.ts.map +1 -0
  16. package/dist/__tests__/demo-hotels.test.js +240 -0
  17. package/dist/__tests__/demo-hotels.test.js.map +1 -0
  18. package/dist/__tests__/server.test.d.ts +2 -0
  19. package/dist/__tests__/server.test.d.ts.map +1 -0
  20. package/dist/__tests__/server.test.js +209 -0
  21. package/dist/__tests__/server.test.js.map +1 -0
  22. package/dist/__tests__/tool-descriptions.test.d.ts +2 -0
  23. package/dist/__tests__/tool-descriptions.test.d.ts.map +1 -0
  24. package/dist/__tests__/tool-descriptions.test.js +139 -0
  25. package/dist/__tests__/tool-descriptions.test.js.map +1 -0
  26. package/dist/__tests__/tools.test.d.ts +2 -0
  27. package/dist/__tests__/tools.test.d.ts.map +1 -0
  28. package/dist/__tests__/tools.test.js +226 -0
  29. package/dist/__tests__/tools.test.js.map +1 -0
  30. package/dist/backend.d.ts +43 -0
  31. package/dist/backend.d.ts.map +1 -0
  32. package/dist/backend.js +2 -0
  33. package/dist/backend.js.map +1 -0
  34. package/dist/card.generated.d.ts +4 -0
  35. package/dist/card.generated.d.ts.map +1 -0
  36. package/dist/card.generated.js +8 -0
  37. package/dist/card.generated.js.map +1 -0
  38. package/dist/config.d.ts +122 -0
  39. package/dist/config.d.ts.map +1 -0
  40. package/dist/config.js +42 -0
  41. package/dist/config.js.map +1 -0
  42. package/dist/data.d.ts +5 -0
  43. package/dist/data.d.ts.map +1 -0
  44. package/dist/data.js +183 -0
  45. package/dist/data.js.map +1 -0
  46. package/dist/dates.d.ts +32 -0
  47. package/dist/dates.d.ts.map +1 -0
  48. package/dist/dates.js +75 -0
  49. package/dist/dates.js.map +1 -0
  50. package/dist/demo-hotels.d.ts +96 -0
  51. package/dist/demo-hotels.d.ts.map +1 -0
  52. package/dist/demo-hotels.js +446 -0
  53. package/dist/demo-hotels.js.map +1 -0
  54. package/dist/fence.d.ts +24 -0
  55. package/dist/fence.d.ts.map +1 -0
  56. package/dist/fence.js +0 -0
  57. package/dist/fence.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +23 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/legal.d.ts +5 -0
  63. package/dist/legal.d.ts.map +1 -0
  64. package/dist/legal.js +111 -0
  65. package/dist/legal.js.map +1 -0
  66. package/dist/money.d.ts +13 -0
  67. package/dist/money.d.ts.map +1 -0
  68. package/dist/money.js +46 -0
  69. package/dist/money.js.map +1 -0
  70. package/dist/server.d.ts +85 -0
  71. package/dist/server.d.ts.map +1 -0
  72. package/dist/server.js +275 -0
  73. package/dist/server.js.map +1 -0
  74. package/dist/surface.d.ts +52 -0
  75. package/dist/surface.d.ts.map +1 -0
  76. package/dist/surface.js +99 -0
  77. package/dist/surface.js.map +1 -0
  78. package/dist/tools.d.ts +336 -0
  79. package/dist/tools.d.ts.map +1 -0
  80. package/dist/tools.js +862 -0
  81. package/dist/tools.js.map +1 -0
  82. package/dist/trip-ids.d.ts +16 -0
  83. package/dist/trip-ids.d.ts.map +1 -0
  84. package/dist/trip-ids.js +77 -0
  85. package/dist/trip-ids.js.map +1 -0
  86. package/dist/trip.d.ts +59 -0
  87. package/dist/trip.d.ts.map +1 -0
  88. package/dist/trip.js +149 -0
  89. package/dist/trip.js.map +1 -0
  90. package/dist/types.d.ts +193 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +13 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/vocab.d.ts +33 -0
  95. package/dist/vocab.d.ts.map +1 -0
  96. package/dist/vocab.js +15 -0
  97. package/dist/vocab.js.map +1 -0
  98. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @curless/hotel-storefront
2
+
3
+ A hotel-booking MCP connector, driven by one config file. Brand, tool names,
4
+ vocabulary and where the rooms come from live in a `HotelConfig`, so the second
5
+ hotel group is a JSON file rather than a fork.
6
+
7
+ ```bash
8
+ npx curless-hotel-storefront ./hotels/shangrila/config.json
9
+ ```
10
+
11
+ It answers on `POST /mcp` (streamable HTTP), serves its own card as a `ui://`
12
+ resource, and publishes `/docs`, `/privacy`, `/terms` and `/healthz`.
13
+
14
+ ## Dates are a first-class type
15
+
16
+ The one structural difference from a shopping connector: **a room has no price
17
+ until someone names the nights.** Everything that can carry a price takes a
18
+ `Stay`, and the parts that cannot — a hotel's summary, its amenities — are
19
+ deliberately separate from the parts that can. A connector that guesses "this
20
+ weekend" books someone the wrong nights, and they find out at the front desk.
21
+
22
+ ## The eleven tools
23
+
24
+ | | |
25
+ |---|---|
26
+ | `list_<brand>_hotels` | Hotels by city or free text. A city usually holds several, and all of them come back |
27
+ | `get_<brand>_hotel` | One hotel; with `checkIn`/`checkOut`, its rooms priced for those nights |
28
+ | `list_<brand>_extras` | Transfers, dining, experiences, spa, family |
29
+ | `add_to_<brand>_trip` · `view_` · `clear_` | The trip: room nights plus add-ons, priced as one |
30
+ | `request_<brand>_booking` | Holds the rooms, issues a confirmation code |
31
+ | `list_my_<brand>_bookings` | |
32
+ | `preview_<brand>_cancellation` · `cancel_<brand>_booking` | The fee and its rule, before anything is cancelled |
33
+ | `get_<brand>_hotel_policies` | What the property itself publishes |
34
+
35
+ Tool descriptions say what a tool does and nothing else; how to behave lives in
36
+ the server's `instructions`, which is what both assistant directories' listing
37
+ attestations require.
38
+
39
+ ## Three rules the code enforces
40
+
41
+ - **No dates, no line.** `add_to_*_trip` asks rather than guessing.
42
+ - **Preview, then cancel.** `cancel_*_booking` returns the fee and its rule and
43
+ cancels nothing until it is called again with `confirmed`.
44
+ - **Held is not paid.** A booking request takes no money and collects no card;
45
+ the answer says so, and the card says so in as many words.
46
+
47
+ ## Where the rooms come from
48
+
49
+ Everything goes through one `HotelBackend` interface. The implementation shipped
50
+ here reads a JSON file and prices it (`DemoHotels`); a group's own booking API
51
+ arrives as a second implementation, and the tools, the card and the evals do not
52
+ learn about it.
53
+
54
+ While the data is a demonstration file, every price-bearing answer carries
55
+ `demo: true` and says so in words — an invented rate presented as bookable is
56
+ the one failure a booking connector cannot have.
57
+
58
+ ## Payment
59
+
60
+ Not in this version, by design: the arc ends at a booking request. The seam is
61
+ `config.payment.mode`.
62
+
63
+ MIT · part of [agentbank](https://mcp.curless.ai/doc/sdks/)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=card-contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-contract.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/card-contract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { HOTEL_CARD_HTML, HOTEL_CARD_SOURCE_SHA } from '../card.generated.js';
6
+ import { toolNames } from '../config.js';
7
+ import { buildOutputSchemas } from '../tools.js';
8
+ // The card is served from `card.generated.ts`, not from `widget/`. Two things
9
+ // can quietly come apart, and both look fine until a guest is looking at the
10
+ // card:
11
+ //
12
+ // 1. the widget is edited and not rebuilt - the committed HTML is then last
13
+ // week's card, and nothing else in the repo notices;
14
+ // 2. a tool learns a new `view` and the card has no renderer for it - the
15
+ // guest gets "this card could not be drawn" for an answer the server was
16
+ // happy with.
17
+ //
18
+ // One test each.
19
+ const widget = fileURLToPath(new URL('../../widget/', import.meta.url));
20
+ describe('the built card', () => {
21
+ it('is the card the widget sources currently produce', () => {
22
+ const dir = `${widget}src`;
23
+ const h = createHash('sha256');
24
+ for (const name of readdirSync(dir).sort()) {
25
+ h.update(name);
26
+ h.update(readFileSync(`${dir}/${name}`));
27
+ }
28
+ h.update('index.html');
29
+ h.update(readFileSync(`${widget}index.html`));
30
+ expect(h.digest('hex'), 'widget/ has changed since the card was built - run `npm run build` in widget/').toBe(HOTEL_CARD_SOURCE_SHA);
31
+ });
32
+ it('is one self-contained page, with nothing fetched from anywhere', () => {
33
+ expect(HOTEL_CARD_HTML.startsWith('<!doctype html>')).toBe(true);
34
+ expect(HOTEL_CARD_HTML).not.toMatch(/<script[^>]+src=["']http/i);
35
+ expect(HOTEL_CARD_HTML).not.toMatch(/<link[^>]+href=["']http/i);
36
+ });
37
+ it('draws every view the tools can answer with', () => {
38
+ const schemas = buildOutputSchemas(toolNames({}));
39
+ const views = new Set();
40
+ for (const schema of Object.values(schemas)) {
41
+ const v = schema
42
+ .properties?.view;
43
+ for (const name of v?.enum ?? (v?.const ? [v.const] : []))
44
+ views.add(name);
45
+ }
46
+ expect(views.size).toBeGreaterThan(10);
47
+ for (const view of views) {
48
+ // `error` is the card's fallback branch rather than a named renderer.
49
+ if (view === 'error')
50
+ continue;
51
+ // A view whose name is a valid identifier comes out of the minifier as a
52
+ // BARE key - `extras:someFn` - so looking only for the quoted string
53
+ // reports a renderer missing that is right there.
54
+ const drawn = HOTEL_CARD_HTML.includes(`"${view}"`) ||
55
+ new RegExp(`\\b${view}\\s*:`).test(HOTEL_CARD_HTML);
56
+ expect(drawn, `the card has no renderer for ${view}`).toBe(true);
57
+ }
58
+ });
59
+ it('says the two things this connector must never stop saying', () => {
60
+ expect(HOTEL_CARD_HTML).toContain('Nothing has been charged');
61
+ expect(HOTEL_CARD_HTML).toContain('No card is taken');
62
+ });
63
+ });
64
+ //# sourceMappingURL=card-contract.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-contract.test.js","sourceRoot":"","sources":["../../src/__tests__/card-contract.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/data.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,89 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { loadHotelData, parseHotelData } from '../data.js';
4
+ import { formatMoney, money, taxOn, toMinor } from '../money.js';
5
+ const SHANGRILA = fileURLToPath(new URL('../../../../../hotels/shangrila/data/hotels.json', import.meta.url));
6
+ const hotel = (over = {}) => ({
7
+ id: 'h1',
8
+ name: 'A Hotel',
9
+ brand: 'Brand',
10
+ city: 'Hong Kong',
11
+ cityId: 'hong-kong',
12
+ summary: 'Somewhere to sleep.',
13
+ amenities: ['Pool'],
14
+ currency: 'HKD',
15
+ taxRate: 0.13,
16
+ weekendUplift: 0.2,
17
+ rooms: [
18
+ {
19
+ id: 'r1',
20
+ name: 'Deluxe',
21
+ sleeps: 2,
22
+ breakfast: false,
23
+ nightly: 1000,
24
+ roomsLeft: 3,
25
+ refundable: true,
26
+ freeCancelHours: 48,
27
+ },
28
+ ],
29
+ ...over,
30
+ });
31
+ const file = (over = {}) => ({ brand: 'Brand', hotels: [hotel(over)] });
32
+ describe('the brand file we actually ship', () => {
33
+ it('parses, and is the nine hotels the demo claims', () => {
34
+ const data = loadHotelData(SHANGRILA);
35
+ expect(data.brand).toBe('Shangri-La');
36
+ expect(data.hotels).toHaveLength(9);
37
+ expect(new Set(data.hotels.map((h) => h.cityId))).toEqual(new Set(['hong-kong', 'shanghai', 'beijing', 'shenzhen']));
38
+ });
39
+ it('gives every room a cancellation story, one way or the other', () => {
40
+ for (const h of loadHotelData(SHANGRILA).hotels) {
41
+ for (const r of h.rooms) {
42
+ expect(r.refundable ? typeof r.freeCancelHours : 'undefined').toBe(r.refundable ? 'number' : 'undefined');
43
+ }
44
+ expect(h.policies?.cancellation, `${h.id} must publish a cancellation policy`).toBeTruthy();
45
+ }
46
+ });
47
+ });
48
+ describe('what the validator refuses', () => {
49
+ it('a tax rate written as a percentage - 16 would be a 1600% tax that still renders', () => {
50
+ expect(() => parseHotelData(file({ taxRate: 16 }))).toThrow(/taxRate is 16/);
51
+ });
52
+ it('a rate typed in minor units, which would look like a real if eye-watering price', () => {
53
+ expect(() => parseHotelData(file({ rooms: [{ ...hotel().rooms[0], nightly: 380_000 }] }))).toThrow(/nightly is 380000/);
54
+ });
55
+ it('"free to cancel" with no deadline, and a deadline on a rate that has none', () => {
56
+ expect(() => parseHotelData(file({ rooms: [{ ...hotel().rooms[0], freeCancelHours: undefined }] }))).toThrow(/must say freeCancelHours/);
57
+ expect(() => parseHotelData(file({ rooms: [{ ...hotel().rooms[0], refundable: false }] }))).toThrow(/free window that does not exist/);
58
+ });
59
+ it('two hotels or two room types sharing an id, which would hold the wrong room', () => {
60
+ expect(() => parseHotelData({ brand: 'B', hotels: [hotel(), hotel()] })).toThrow(/share the id h1/);
61
+ expect(() => parseHotelData(file({ rooms: [hotel().rooms[0], { ...hotel().rooms[0], name: 'Other' }] }))).toThrow(/share the id r1/);
62
+ });
63
+ it('a hotel nobody can book, and a policy topic nothing would display', () => {
64
+ expect(() => parseHotelData(file({ rooms: [] }))).toThrow(/no room types/);
65
+ expect(() => parseHotelData(file({ policies: { parking: 'Free.' } }))).toThrow(/topic parking/);
66
+ });
67
+ it('a rating with no count behind it', () => {
68
+ expect(() => parseHotelData(file({ rating: { value: 4.8, scaleMax: 5 } }))).toThrow(/rating.count/);
69
+ });
70
+ it('a file that is empty, or missing the brand it belongs to', () => {
71
+ expect(() => parseHotelData({ hotels: [] })).toThrow(/no hotels/);
72
+ expect(() => parseHotelData({ hotels: [hotel()] })).toThrow(/brand/);
73
+ });
74
+ });
75
+ describe('money', () => {
76
+ it('holds prices as integer minor units, so a tax rate cannot produce 4293.999999', () => {
77
+ expect(toMinor(3800, 'HKD')).toBe(380_000);
78
+ expect(toMinor(3800, 'JPY')).toBe(3800);
79
+ expect(taxOn(money(380_000, 'HKD'), 0.13)).toEqual({ minor: 49_400, currency: 'HKD' });
80
+ });
81
+ it('writes money the way the guest reads it', () => {
82
+ expect(formatMoney(money(380_000, 'HKD'))).toBe('HK$3,800');
83
+ expect(formatMoney(money(1_030_560, 'HKD'))).toBe('HK$10,305.60');
84
+ expect(formatMoney(money(145_000, 'CNY'))).toBe('¥1,450');
85
+ expect(formatMoney(money(12_000, 'JPY'))).toBe('¥12,000');
86
+ expect(formatMoney(money(5000, 'AED'))).toBe('50 AED');
87
+ });
88
+ });
89
+ //# sourceMappingURL=data.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data.test.js","sourceRoot":"","sources":["../../src/__tests__/data.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dates.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/dates.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { addDays, eachNight, isWeekendNight, nights, stayProblem, todayAt } from '../dates.js';
3
+ describe('nights', () => {
4
+ it('counts nights, not days - check-out is exclusive', () => {
5
+ expect(nights('2026-10-12', '2026-10-14')).toBe(2);
6
+ expect(nights('2026-10-12', '2026-10-13')).toBe(1);
7
+ });
8
+ it('crosses a month, a year and a leap day without losing one', () => {
9
+ expect(nights('2026-10-30', '2026-11-02')).toBe(3);
10
+ expect(nights('2026-12-30', '2027-01-02')).toBe(3);
11
+ expect(nights('2028-02-27', '2028-03-01')).toBe(3);
12
+ });
13
+ it('survives the day a +08:00 city changes date before UTC does', () => {
14
+ // 2026-10-11T17:00Z is already the 12th in Hong Kong.
15
+ expect(todayAt(Date.parse('2026-10-11T17:00:00Z'), '+08:00')).toBe('2026-10-12');
16
+ expect(todayAt(Date.parse('2026-10-11T17:00:00Z'), '+00:00')).toBe('2026-10-11');
17
+ expect(todayAt(Date.parse('2026-10-12T03:00:00Z'), '-05:00')).toBe('2026-10-11');
18
+ });
19
+ });
20
+ describe('eachNight', () => {
21
+ it('lists the nights slept in, never the departure day', () => {
22
+ expect(eachNight('2026-10-12', '2026-10-15')).toEqual([
23
+ '2026-10-12',
24
+ '2026-10-13',
25
+ '2026-10-14',
26
+ ]);
27
+ });
28
+ it('returns nothing rather than looping when the dates are backwards', () => {
29
+ expect(eachNight('2026-10-15', '2026-10-12')).toEqual([]);
30
+ expect(eachNight('2026-10-12', '2026-10-12')).toEqual([]);
31
+ });
32
+ });
33
+ describe('isWeekendNight', () => {
34
+ it('is the Friday and Saturday nights', () => {
35
+ // 2026-10-16 is a Friday.
36
+ expect(['2026-10-15', '2026-10-16', '2026-10-17', '2026-10-18'].map(isWeekendNight)).toEqual([
37
+ false,
38
+ true,
39
+ true,
40
+ false,
41
+ ]);
42
+ });
43
+ });
44
+ describe('stayProblem', () => {
45
+ const today = '2026-10-01';
46
+ it('passes a plain stay', () => {
47
+ expect(stayProblem({ checkIn: '2026-10-12', checkOut: '2026-10-14', guests: 2 }, today)).toBeNull();
48
+ });
49
+ it('will not book yesterday, and says what today is', () => {
50
+ const why = stayProblem({ checkIn: '2026-09-30', checkOut: '2026-10-02', guests: 2 }, today);
51
+ expect(why).toContain('2026-09-30');
52
+ expect(why).toContain('2026-10-01');
53
+ });
54
+ it('rejects a zero-night stay in words a guest can act on', () => {
55
+ expect(stayProblem({ checkIn: '2026-10-12', checkOut: '2026-10-12', guests: 1 }, today)).toContain('Check-out has to be after check-in');
56
+ });
57
+ it('rejects a date that is not a date, including 31 September', () => {
58
+ expect(stayProblem({ checkIn: 'next tuesday', checkOut: '2026-10-14', guests: 1 }, today)).toContain('2026-10-12');
59
+ expect(stayProblem({ checkIn: '2026-09-31', checkOut: '2026-10-02', guests: 1 }, today)).toContain('Check-in');
60
+ });
61
+ it('rejects a guest count that is missing, fractional or absurd', () => {
62
+ expect(stayProblem({ checkIn: '2026-10-12', checkOut: '2026-10-14' }, today)).toContain('how many guests');
63
+ expect(stayProblem({ checkIn: '2026-10-12', checkOut: '2026-10-14', guests: 1.5 }, today)).toContain('how many guests');
64
+ expect(stayProblem({ checkIn: '2026-10-12', checkOut: '2026-10-14', guests: 40 }, today)).toContain('the hotel directly');
65
+ });
66
+ it('refuses a stay longer than it can quote, and one further out than it knows', () => {
67
+ expect(stayProblem({ checkIn: '2026-10-12', checkOut: '2026-12-12', guests: 2 }, today)).toContain('61');
68
+ expect(stayProblem({ checkIn: addDays(today, 400), checkOut: addDays(today, 402), guests: 2 }, today)).toContain('365 days');
69
+ });
70
+ });
71
+ //# sourceMappingURL=dates.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dates.test.js","sourceRoot":"","sources":["../../src/__tests__/dates.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=demo-hotels.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo-hotels.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/demo-hotels.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,240 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { beforeEach, describe, expect, it } from 'vitest';
3
+ import { loadHotelData } from '../data.js';
4
+ import { createDemoHotels } from '../demo-hotels.js';
5
+ // The brand's REAL data file, not a fixture: a demo backend whose tests pass on
6
+ // invented input tells us nothing about what the connector will actually serve,
7
+ // and this doubles as the guard that hotels.json still parses.
8
+ const DATA = loadHotelData(fileURLToPath(new URL('../../../../../hotels/shangrila/data/hotels.json', import.meta.url)));
9
+ const OCT_1 = Date.parse('2026-10-01T02:00:00Z'); // 10:00 in Hong Kong
10
+ const MON_WED = { checkIn: '2026-10-19', checkOut: '2026-10-21', guests: 2 };
11
+ const FRI_SUN = { checkIn: '2026-10-16', checkOut: '2026-10-18', guests: 2 };
12
+ const me = { sessionId: 'session-a' };
13
+ const guest = { name: 'Robin Ruan', email: 'robin@example.com' };
14
+ let clock = OCT_1;
15
+ const make = () => createDemoHotels({ data: DATA, now: () => clock, rng: () => 0.5 });
16
+ beforeEach(() => {
17
+ clock = OCT_1;
18
+ });
19
+ describe('searching', () => {
20
+ it('answers a city with every hotel in it - a city is not a hotel', async () => {
21
+ const found = await make().searchHotels({ city: 'hong-kong' });
22
+ expect(found).toHaveLength(4);
23
+ expect(new Set(found.map((h) => h.brand))).toEqual(new Set(['Shangri-La', 'Kerry', 'JEN']));
24
+ });
25
+ it('matches free text across name, area and amenities', async () => {
26
+ const hotels = make();
27
+ expect((await hotels.searchHotels({ query: 'pudong' })).map((h) => h.id)).toEqual([
28
+ 'shanghai-pudong',
29
+ ]);
30
+ expect((await hotels.searchHotels({ query: 'harbour views' })).length).toBeGreaterThan(1);
31
+ expect(await hotels.searchHotels({ query: 'reykjavik' })).toEqual([]);
32
+ });
33
+ it('shows a from-price per hotel but no stay price, because nobody gave dates', async () => {
34
+ const [jen] = await make().searchHotels({ query: 'jen hong kong' });
35
+ expect(jen?.fromNightly).toEqual({ minor: 89_000, currency: 'HKD' }); // the pay-now rate, HK$890
36
+ });
37
+ });
38
+ describe('a hotel, with and without dates', () => {
39
+ it('has no rooms and no prices until someone names the nights', async () => {
40
+ const detail = await make().getHotel('hong-kong-island');
41
+ expect(detail?.hotel.name).toBe('Island Shangri-La, Hong Kong');
42
+ expect(detail?.rooms).toBeUndefined();
43
+ expect(detail?.stay).toBeUndefined();
44
+ });
45
+ it('prices the stay exactly, tax split out', async () => {
46
+ const detail = await make().getHotel('hong-kong-island', MON_WED);
47
+ const room = detail?.rooms?.find((r) => r.id === 'deluxe-city');
48
+ expect(room).toMatchObject({
49
+ nights: 2,
50
+ nightly: { minor: 380_000, currency: 'HKD' }, // HK$3,800 a night, no weekend in it
51
+ taxesAndFees: { minor: 98_800, currency: 'HKD' }, // 13% of HK$7,600
52
+ stayTotal: { minor: 858_800, currency: 'HKD' },
53
+ });
54
+ });
55
+ it('charges the Friday and Saturday more, and says so in the average', async () => {
56
+ const detail = await make().getHotel('hong-kong-island', FRI_SUN);
57
+ const room = detail?.rooms?.find((r) => r.id === 'deluxe-city');
58
+ // HK$3,800 x 1.2 on both nights.
59
+ expect(room?.nightly).toEqual({ minor: 456_000, currency: 'HKD' });
60
+ expect(room?.stayTotal).toEqual({ minor: 1_030_560, currency: 'HKD' });
61
+ });
62
+ it('gives a free-cancellation deadline counted back from arrival, and none on a rate that has none', async () => {
63
+ const rooms = (await make().getHotel('hong-kong-island', MON_WED))?.rooms ?? [];
64
+ // 48 hours before 15:00 on the 19th, Hong Kong time.
65
+ expect(rooms.find((r) => r.id === 'deluxe-city')?.freeCancellationUntil).toBe('2026-10-17T07:00:00.000Z');
66
+ expect(rooms.find((r) => r.id === 'horizon-club')?.freeCancellationUntil).toBeUndefined();
67
+ expect(rooms.find((r) => r.id === 'horizon-club')?.refundable).toBe(false);
68
+ });
69
+ it('says why rather than showing a room that cannot hold the party', async () => {
70
+ const detail = await make().getHotel('hong-kong-island', { ...MON_WED, guests: 5 });
71
+ expect(detail?.rooms).toBeUndefined();
72
+ expect(detail?.noRooms).toContain('largest is 3');
73
+ });
74
+ it('hands a bad date straight back instead of pricing something else', async () => {
75
+ const detail = await make().getHotel('hong-kong-island', {
76
+ checkIn: '2026-09-01',
77
+ checkOut: '2026-09-03',
78
+ guests: 2,
79
+ });
80
+ expect(detail?.noRooms).toContain('in the past');
81
+ expect(detail?.rooms).toBeUndefined();
82
+ });
83
+ it('finds a hotel by its name as well as its id, because a model will type the name', async () => {
84
+ expect((await make().getHotel('Kowloon Shangri-La, Hong Kong'))?.hotel.id).toBe('hong-kong-kowloon');
85
+ expect(await make().getHotel('a hotel in paris')).toBeNull();
86
+ });
87
+ });
88
+ describe('pricing a trip', () => {
89
+ it('adds add-ons by their own unit and taxes the lot once', async () => {
90
+ const quote = await make().quote({
91
+ lines: [
92
+ { kind: 'room', hotelId: 'hong-kong-island', roomId: 'deluxe-city', stay: MON_WED },
93
+ { kind: 'extra', hotelId: 'hong-kong-island', extraId: 'peak-walk', quantity: 2 },
94
+ ],
95
+ });
96
+ // HK$7,600 of room + HK$680 x 2 of walk, then 13% on top.
97
+ expect(quote.subtotal).toEqual({ minor: 896_000, currency: 'HKD' });
98
+ expect(quote.taxesAndFees).toEqual({ minor: 116_480, currency: 'HKD' });
99
+ expect(quote.total).toEqual({ minor: 1_012_480, currency: 'HKD' });
100
+ expect(quote.stay).toEqual(MON_WED);
101
+ expect(quote.lines.find((l) => l.kind === 'extra')?.quantity).toBe(2);
102
+ });
103
+ it('refuses to invent an exchange rate when a trip crosses currencies', async () => {
104
+ const quote = await make().quote({
105
+ lines: [
106
+ { kind: 'room', hotelId: 'hong-kong-island', roomId: 'deluxe-city', stay: MON_WED },
107
+ { kind: 'room', hotelId: 'shanghai-pudong', roomId: 'deluxe-city', stay: MON_WED },
108
+ ],
109
+ });
110
+ expect(quote.lines).toHaveLength(1);
111
+ expect(quote.dropped?.[0]?.why).toContain('exchange rate');
112
+ expect(quote.total.currency).toBe('HKD');
113
+ });
114
+ it('drops what is gone with a reason rather than quietly repricing', async () => {
115
+ const quote = await make().quote({
116
+ lines: [{ kind: 'room', hotelId: 'hong-kong-island', roomId: 'presidential', stay: MON_WED }],
117
+ });
118
+ expect(quote.lines).toHaveLength(0);
119
+ expect(quote.dropped?.[0]?.why).toContain('no longer listed');
120
+ expect(quote.total.minor).toBe(0);
121
+ });
122
+ });
123
+ describe('requesting a booking', () => {
124
+ const trip = {
125
+ lines: [
126
+ {
127
+ kind: 'room',
128
+ hotelId: 'hong-kong-island',
129
+ roomId: 'suite',
130
+ stay: { ...MON_WED, guests: 3 },
131
+ },
132
+ ],
133
+ };
134
+ it('takes no money, holds the room and says when the hold lapses', async () => {
135
+ const booking = await make().requestBooking(trip, guest, me);
136
+ expect(booking.status).toBe('requested');
137
+ expect(booking.confirmationCode).toMatch(/^SL-[A-Z0-9]{6}$/);
138
+ expect(booking.holdUntil).toBe('2026-10-02T02:00:00.000Z'); // 24 hours
139
+ expect(booking.quote.total.minor).toBeGreaterThan(0);
140
+ });
141
+ it('never holds a room past the moment the guest could walk in', async () => {
142
+ clock = Date.parse('2026-10-19T04:00:00Z'); // arrival is 15:00 in Hong Kong, 07:00Z
143
+ const booking = await make().requestBooking(trip, guest, me);
144
+ expect(booking.holdUntil).toBe('2026-10-19T05:00:00.000Z');
145
+ });
146
+ it('takes the held room off what the next guest is shown, and gives it back when the hold lapses', async () => {
147
+ const hotels = make();
148
+ await hotels.requestBooking(trip, guest, me); // the only suite
149
+ const held = await hotels.getHotel('hong-kong-island', { ...MON_WED, guests: 3 });
150
+ expect(held?.rooms).toEqual([]);
151
+ expect(held?.noRooms).toContain('taken');
152
+ clock = Date.parse('2026-10-03T00:00:00Z'); // past the 24-hour hold
153
+ const back = await hotels.getHotel('hong-kong-island', { ...MON_WED, guests: 3 });
154
+ expect(back?.rooms?.map((r) => r.id)).toEqual(['suite']);
155
+ });
156
+ it('will not book a trip with nothing to sleep in', async () => {
157
+ await expect(make().requestBooking({
158
+ lines: [
159
+ { kind: 'extra', hotelId: 'hong-kong-island', extraId: 'peak-walk', quantity: 1 },
160
+ ],
161
+ }, guest, me)).rejects.toThrow(/nothing to book/);
162
+ });
163
+ });
164
+ describe('cancelling', () => {
165
+ const flexible = {
166
+ lines: [
167
+ { kind: 'room', hotelId: 'hong-kong-island', roomId: 'deluxe-city', stay: MON_WED },
168
+ ],
169
+ };
170
+ const nonRefundable = {
171
+ lines: [
172
+ { kind: 'room', hotelId: 'hong-kong-island', roomId: 'horizon-club', stay: MON_WED },
173
+ ],
174
+ };
175
+ it('costs nothing inside the free window, and says when that window closes', async () => {
176
+ const hotels = make();
177
+ const booking = await hotels.requestBooking(flexible, guest, me);
178
+ const preview = await hotels.previewCancellation(booking.confirmationCode, me);
179
+ expect(preview?.cancellable).toBe(true);
180
+ expect(preview?.fee).toEqual({ minor: 0, currency: 'HKD' });
181
+ expect(preview?.refund).toEqual({ minor: 858_800, currency: 'HKD' });
182
+ expect(preview?.freeUntil).toBe('2026-10-17T07:00:00.000Z');
183
+ expect(preview?.rule).toContain('Nothing has been charged');
184
+ });
185
+ it('charges one night plus tax once the window has closed', async () => {
186
+ clock = Date.parse('2026-10-18T09:00:00Z'); // past 48 hours before arrival
187
+ const hotels = make();
188
+ const booking = await hotels.requestBooking(flexible, guest, me);
189
+ const preview = await hotels.previewCancellation(booking.id, me);
190
+ expect(preview?.fee).toEqual({ minor: 429_400, currency: 'HKD' }); // HK$3,800 + 13%
191
+ expect(preview?.refund).toEqual({ minor: 429_400, currency: 'HKD' });
192
+ expect(preview?.rule).toContain('one night plus tax');
193
+ });
194
+ it('charges the whole stay on a non-refundable rate, and says which rate that is', async () => {
195
+ const hotels = make();
196
+ const booking = await hotels.requestBooking(nonRefundable, guest, me);
197
+ const preview = await hotels.previewCancellation(booking.confirmationCode, me);
198
+ expect(preview?.fee).toEqual({ minor: 1_333_400, currency: 'HKD' });
199
+ expect(preview?.refund).toEqual({ minor: 0, currency: 'HKD' });
200
+ expect(preview?.rule).toContain('non-refundable');
201
+ });
202
+ it('records the fee it previewed, and releases the room', async () => {
203
+ const hotels = make();
204
+ const booking = await hotels.requestBooking(flexible, guest, me);
205
+ const preview = await hotels.previewCancellation(booking.id, me);
206
+ const cancelled = await hotels.cancelBooking(booking.id, me, 'flight moved');
207
+ expect(cancelled?.status).toBe('cancelled');
208
+ expect(cancelled?.cancellationFee).toEqual(preview?.fee);
209
+ expect(cancelled?.guest.notes).toContain('flight moved');
210
+ expect(await hotels.previewCancellation(booking.id, me)).toMatchObject({ cancellable: false });
211
+ });
212
+ it('is nobody else’s business', async () => {
213
+ const hotels = make();
214
+ const booking = await hotels.requestBooking(flexible, guest, me);
215
+ const someoneElse = { sessionId: 'session-b' };
216
+ expect(await hotels.previewCancellation(booking.id, someoneElse)).toBeNull();
217
+ expect(await hotels.cancelBooking(booking.id, someoneElse)).toBeNull();
218
+ expect(await hotels.listBookings(someoneElse)).toEqual([]);
219
+ // Unless they can name the email the booking was made with.
220
+ expect(await hotels.listBookings({ sessionId: 'session-b', email: guest.email })).toHaveLength(1);
221
+ });
222
+ });
223
+ describe('policies', () => {
224
+ it('gives the hotel its own words, in a fixed order', async () => {
225
+ const policies = await make().policies('hong-kong-island');
226
+ expect(policies.map((p) => p.topic)).toEqual([
227
+ 'cancellation',
228
+ 'check-in',
229
+ 'children',
230
+ 'pets',
231
+ 'deposit',
232
+ 'accessibility',
233
+ ]);
234
+ expect(policies[0]?.text).toContain('48 hours');
235
+ });
236
+ it('answers an unknown hotel with nothing rather than a guess', async () => {
237
+ expect(await make().policies('hotel-du-nowhere')).toEqual([]);
238
+ });
239
+ });
240
+ //# sourceMappingURL=demo-hotels.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo-hotels.test.js","sourceRoot":"","sources":["../../src/__tests__/demo-hotels.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/server.test.ts"],"names":[],"mappings":""}