sabre-client 0.6.0 → 0.7.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.
package/dist/testing.js CHANGED
@@ -1,6 +1,259 @@
1
1
  // src/testing/queue-fake.ts
2
2
  import { XMLParser as XMLParser3 } from "fast-xml-parser";
3
3
 
4
+ // src/testing/booking-fake.ts
5
+ var LOCATOR = /^[A-Z0-9]{6}$/;
6
+ var FULFILL_REASONS = /* @__PURE__ */ new Set([
7
+ "UNABLE_TO_RETRIEVE_TICKETS",
8
+ "FULFILLMENT_NOT_CONFIRMED",
9
+ "INTERNAL_SERVER_TIMEOUT",
10
+ "DOWNLINE_SERVICE_ERROR",
11
+ "PAYMENT_CARD_ISSUE",
12
+ "NOT_COMMITTED",
13
+ "TRANSPORT",
14
+ "GATEWAY_5XX",
15
+ "UNPARSEABLE",
16
+ "ABORTED"
17
+ ]);
18
+ var ISSUES_BY_DEFAULT = /* @__PURE__ */ new Set(["UNABLE_TO_RETRIEVE_TICKETS", "INTERNAL_SERVER_TIMEOUT", "NOT_COMMITTED", "TRANSPORT", "GATEWAY_5XX", "UNPARSEABLE", "ABORTED"]);
19
+ var VOID_REASONS = /* @__PURE__ */ new Set(["END_TRANSACTION_PROBLEM", "TRANSPORT", "GATEWAY_5XX", "UNPARSEABLE", "ABORTED"]);
20
+ var DOWNLINE_TEXT = {
21
+ INVALID_TICKET_STOCK: "The AirTicketRQ service returned an error: (DesignatePrinterLLSRQ: INVALID TICKET STOCK-0096).",
22
+ COMMISSION_REQUIRED: "The AirTicketRQ service returned an error: (AirTicketLLS failed for /Ticketing[1] with Cause: AirTicketLLSRQ: COMMISSION REQUIRED-0260, TicketingDocumentServicesRQ: No new tickets have been issued).",
23
+ HARDCOPY_PRINTER: "The AirTicketRQ service returned an error: (AirTicketLLS failed for /Ticketing[1] with Cause: AirTicketLLSRQ: ASSIGN HARDCOPY PRINTER FOR ETR DOCUMENTS-USE PTR/-0052, TicketingDocumentServicesRQ: No new tickets have been issued).",
24
+ SECURE_FLIGHT: "The AirTicketRQ service returned an error: (AirTicketLLS failed for /Ticketing[1] with Cause: AirTicketLLSRQ: PASSENGER SECURITY DATA REQUIRED PLEASE UPDATE AND RETRY-1829, TicketingDocumentServicesRQ: No new tickets have been issued)."
25
+ };
26
+ var UNCERTAIN_TEXT = {
27
+ UNABLE_TO_RETRIEVE_TICKETS: ["APPLICATION_ERROR", "Tickets might have been issued successfully but application could not retrieve them. Verify booking details and/or audit trail reports (DQB)."],
28
+ FULFILLMENT_NOT_CONFIRMED: ["WARNING", "The fulfillment operation cannot be confirmed. No new tickets were found in the booking."],
29
+ INTERNAL_SERVER_TIMEOUT: ["APPLICATION_ERROR", "The downline ticketing service exceeded its processing time and the request timed out. Please retry the fulfillment attempt."],
30
+ DOWNLINE_SERVICE_ERROR: ["APPLICATION_ERROR", "The AirTicketRQ service returned an error: (AirTicketLLSRQ: UNABLE TO PROCESS)."],
31
+ PAYMENT_CARD_ISSUE: ["APPLICATION_ERROR", "At least one fulfillment operation was not processed correctly. The transaction was declined due to an invalid card number."]
32
+ };
33
+ function fail(message) {
34
+ throw new TypeError(`sabre-client/testing: ${message}`);
35
+ }
36
+ var json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
37
+ var appError = (type, description, category = "APPLICATION_ERROR") => ({ category, type, description });
38
+ function toTicket(value, label, today) {
39
+ if (typeof value !== "object" || value === null) fail(`${label} debe ser un objeto`);
40
+ const ticket = value;
41
+ if (typeof ticket.number !== "string" || !/^[0-9A-Z]{13}$/.test(ticket.number)) fail(`${label}.number debe ser un n\xFAmero de boleto de 13 caracteres`);
42
+ if (ticket.ticketStatusCode !== void 0 && ticket.ticketStatusCode !== "TE" && ticket.ticketStatusCode !== "TV") fail(`${label}.ticketStatusCode debe ser TE o TV`);
43
+ return { number: ticket.number, ticketStatusCode: ticket.ticketStatusCode ?? "TE", date: ticket.date ?? today, committed: true };
44
+ }
45
+ function validateFulfillOutcome(value, label) {
46
+ if (value === "issued" || value === "partial") return value;
47
+ if (typeof value !== "object" || value === null) fail(`${label} debe ser 'issued', 'partial' o un objeto`);
48
+ const outcome = value;
49
+ if ("uncertain" in outcome) {
50
+ if (!FULFILL_REASONS.has(String(outcome.uncertain))) fail(`${label}.uncertain no es una reason de tickets.fulfill`);
51
+ } else if ("downline" in outcome) {
52
+ if (!(String(outcome.downline) in DOWNLINE_TEXT)) fail(`${label}.downline debe ser ${Object.keys(DOWNLINE_TEXT).join(", ")}`);
53
+ } else if (typeof outcome.error !== "string" || outcome.error === "") {
54
+ fail(`${label} debe tener uncertain, downline o error`);
55
+ }
56
+ return value;
57
+ }
58
+ function validateVoidOutcome(value, label) {
59
+ if (value === "voided") return value;
60
+ if (typeof value !== "object" || value === null || !VOID_REASONS.has(String(value.uncertain))) {
61
+ fail(`${label} debe ser 'voided' o { uncertain: ${[...VOID_REASONS].join(" | ")} }`);
62
+ }
63
+ return value;
64
+ }
65
+ function createBookingHandler(options, now, record) {
66
+ const today = () => now().toISOString().slice(0, 10);
67
+ const bookings = /* @__PURE__ */ new Map();
68
+ for (const [locator, seed] of Object.entries(options.bookings ?? {})) {
69
+ if (!LOCATOR.test(locator)) fail(`bookings['${locator}']: la clave debe ser un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
70
+ if (typeof seed !== "object" || seed === null) fail(`bookings['${locator}'] debe ser un objeto`);
71
+ if (seed.flights !== void 0 && (!Number.isInteger(seed.flights) || seed.flights < 0)) fail(`bookings['${locator}'].flights debe ser un entero >= 0`);
72
+ if (seed.tickets !== void 0 && !Array.isArray(seed.tickets)) fail(`bookings['${locator}'].tickets debe ser una lista`);
73
+ bookings.set(locator, {
74
+ flights: seed.flights ?? 1,
75
+ tickets: (seed.tickets ?? []).map((ticket, index) => toTicket(ticket, `bookings['${locator}'].tickets[${index}]`, today()))
76
+ });
77
+ }
78
+ const ticketing = options.ticketing ?? {};
79
+ if (typeof ticketing !== "object" || ticketing === null) fail("ticketing debe ser un objeto");
80
+ if (ticketing.fulfill !== void 0 && !Array.isArray(ticketing.fulfill)) fail("ticketing.fulfill debe ser una lista");
81
+ if (ticketing.void !== void 0 && !Array.isArray(ticketing.void)) fail("ticketing.void debe ser una lista");
82
+ const fulfillQueue = (ticketing.fulfill ?? []).map((outcome, index) => validateFulfillOutcome(outcome, `ticketing.fulfill[${index}]`));
83
+ const voidQueue = (ticketing.void ?? []).map((outcome, index) => validateVoidOutcome(outcome, `ticketing.void[${index}]`));
84
+ let created = 0;
85
+ let issuedCount = 0;
86
+ const nextLocator = () => {
87
+ let locator;
88
+ do {
89
+ created += 1;
90
+ locator = `FAKE${String.fromCharCode(64 + Math.ceil(created / 26))}${String.fromCharCode(64 + ((created - 1) % 26 + 1))}`;
91
+ } while (bookings.has(locator));
92
+ return locator;
93
+ };
94
+ const nextTicket = () => {
95
+ issuedCount += 1;
96
+ return { number: `999${String(issuedCount).padStart(10, "0")}`, ticketStatusCode: "TE", date: today(), committed: true };
97
+ };
98
+ const ticketJson = (ticket) => ({
99
+ number: ticket.number,
100
+ date: ticket.date,
101
+ airlineCode: "XX",
102
+ travelerIndex: 1,
103
+ flightCoupons: [],
104
+ payment: { total: "100.00", currencyCode: "USD" },
105
+ ticketStatusName: ticket.ticketStatusCode === "TE" ? "Issued" : "Voided",
106
+ ticketStatusCode: ticket.ticketStatusCode
107
+ });
108
+ const bookingJson = (locator, booking) => ({
109
+ bookingId: locator,
110
+ startDate: today(),
111
+ isTicketed: booking.tickets.some((ticket) => ticket.committed),
112
+ flights: Array.from({ length: booking.flights }, (_, index) => ({ itemId: String(index + 1), flightNumber: 100 + index, airlineCode: "XX" })),
113
+ travelers: [{ givenName: "TEST", surname: "PASSENGER" }],
114
+ flightTickets: booking.tickets.filter((ticket) => ticket.committed).map(ticketJson)
115
+ });
116
+ const notFound = () => json(200, { errors: [appError("BOOKING_NOT_FOUND", "Booking cannot be found.", "RESOURCE_NOT_FOUND")] });
117
+ const parse = (body) => {
118
+ try {
119
+ const value = JSON.parse(body);
120
+ return typeof value === "object" && value !== null ? value : {};
121
+ } catch {
122
+ return {};
123
+ }
124
+ };
125
+ const fulfill = (request) => {
126
+ const locator = String(request.confirmationId ?? "");
127
+ record("fulfill", locator);
128
+ const booking = bookings.get(locator);
129
+ if (booking === void 0) return notFound();
130
+ const outcome = fulfillQueue.shift() ?? "issued";
131
+ const echo = { ...request };
132
+ const issue = (committed = true) => {
133
+ const ticket = { ...nextTicket(), committed };
134
+ booking.tickets.push(ticket);
135
+ return [ticket];
136
+ };
137
+ const ticketsOut = (tickets2) => tickets2.map((ticket) => ({ ...ticketJson(ticket), isCommitted: ticket.committed, travelerGivenName: "TEST", travelerSurname: "PASSENGER" }));
138
+ if (typeof outcome === "object" && "error" in outcome) {
139
+ return json(200, { request: echo, errors: [appError(outcome.error, outcome.description ?? `No new tickets have been issued (${outcome.error}).`)] });
140
+ }
141
+ if (typeof outcome === "object" && "downline" in outcome) {
142
+ return json(200, { request: echo, errors: [appError("DOWNLINE_SERVICE_ERROR", DOWNLINE_TEXT[outcome.downline])] });
143
+ }
144
+ if (outcome === "issued" || outcome === "partial") {
145
+ if (booking.tickets.some((ticket) => ticket.ticketStatusCode === "TE")) {
146
+ return json(200, { request: echo, errors: [appError("BOOKING_FULFILLED", "The fulfillment operation cannot be processed. The booking is already fulfilled.")] });
147
+ }
148
+ const tickets2 = issue();
149
+ return json(200, {
150
+ timestamp: now().toISOString().slice(0, 19),
151
+ tickets: ticketsOut(tickets2),
152
+ request: echo,
153
+ ...outcome === "partial" ? { warnings: [appError("PARTIAL_FULFILLMENT", "At least one fulfillment operation was not processed correctly. Ticket issuance was unsuccessful due to a missing form of identification (FOID).", "WARNING")] } : {}
154
+ });
155
+ }
156
+ const { uncertain: reason } = outcome;
157
+ const issues = outcome.issued ?? ISSUES_BY_DEFAULT.has(reason);
158
+ const tickets = issues ? issue(reason !== "NOT_COMMITTED") : [];
159
+ switch (reason) {
160
+ case "TRANSPORT":
161
+ return "transport";
162
+ case "ABORTED":
163
+ return "abort";
164
+ case "GATEWAY_5XX":
165
+ return json(503, { errorCode: "ERR.2SG.PROVIDER.ERROR", message: "sabre-client/testing: 503 forzado" });
166
+ case "UNPARSEABLE":
167
+ return new Response("<html>sabre-client/testing: cuerpo ilegible forzado</html>", { status: 200 });
168
+ case "NOT_COMMITTED":
169
+ return json(200, { timestamp: now().toISOString().slice(0, 19), tickets: ticketsOut(tickets), request: echo });
170
+ default: {
171
+ const [category, description] = UNCERTAIN_TEXT[reason];
172
+ const item = appError(reason, description, category);
173
+ return json(200, { request: echo, ...tickets.length > 0 ? { tickets: ticketsOut(tickets) } : {}, ...category === "WARNING" ? { warnings: [item] } : { errors: [item] } });
174
+ }
175
+ }
176
+ };
177
+ const cancel = (request) => {
178
+ const locator = String(request.confirmationId ?? "");
179
+ record("cancelBooking", locator);
180
+ const booking = bookings.get(locator);
181
+ if (booking === void 0) return notFound();
182
+ const echo = { errorHandlingPolicy: "HALT_ON_ERROR", ...request };
183
+ const live = booking.tickets.filter((ticket) => ticket.ticketStatusCode === "TE");
184
+ if (request.flightTicketOperation === "VOID") {
185
+ if (live.length === 0) {
186
+ return json(200, {
187
+ request: echo,
188
+ errors: [
189
+ appError("NO_ITEMS_CANCELLED", "Nothing was cancelled - cancellation was interrupted due to errors", "WARNING"),
190
+ appError("UNABLE_TO_VOID_TICKET", "Unable to void ticket.", "CANCELLATION_ERROR")
191
+ ]
192
+ });
193
+ }
194
+ const outcome = voidQueue.shift() ?? "voided";
195
+ const voids = outcome === "voided" || outcome.voided !== false;
196
+ if (voids) {
197
+ for (const ticket of live) ticket.ticketStatusCode = "TV";
198
+ if (request.cancelAll === true) booking.flights = 0;
199
+ }
200
+ const voidedTickets = voids ? live.map((ticket) => ticket.number) : [];
201
+ if (outcome !== "voided") {
202
+ switch (outcome.uncertain) {
203
+ case "TRANSPORT":
204
+ return "transport";
205
+ case "ABORTED":
206
+ return "abort";
207
+ case "GATEWAY_5XX":
208
+ return json(503, { errorCode: "ERR.2SG.PROVIDER.ERROR", message: "sabre-client/testing: 503 forzado" });
209
+ case "UNPARSEABLE":
210
+ return new Response("<html>sabre-client/testing: cuerpo ilegible forzado</html>", { status: 200 });
211
+ case "END_TRANSACTION_PROBLEM":
212
+ return json(200, { request: echo, voidedTickets, errors: [appError("END_TRANSACTION_PROBLEM", "Confirmation is pending. Redisplay the booking", "WARNING")] });
213
+ }
214
+ }
215
+ return json(200, {
216
+ timestamp: now().toISOString().slice(0, 19),
217
+ request: echo,
218
+ voidedTickets,
219
+ ...request.retrieveBooking === true ? { booking: bookingJson(locator, booking) } : {}
220
+ });
221
+ }
222
+ if (request.cancelAll === true) booking.flights = 0;
223
+ return json(200, { timestamp: now().toISOString().slice(0, 19), request: echo, ...request.retrieveBooking === true ? { booking: bookingJson(locator, booking) } : {} });
224
+ };
225
+ return (url, body) => {
226
+ const path = new URL(url, "https://fake.invalid").pathname;
227
+ if (path.endsWith("/auth/token")) {
228
+ record("token");
229
+ return json(200, { access_token: "FAKE-TOKEN", token_type: "bearer", expires_in: 604800 });
230
+ }
231
+ if (!path.startsWith("/v1/trip/orders/")) return void 0;
232
+ const request = parse(body);
233
+ switch (path.slice("/v1/trip/orders/".length)) {
234
+ case "createBooking": {
235
+ const locator = nextLocator();
236
+ record("createBooking", locator);
237
+ const flights = (request.flightDetails?.flights ?? []).length;
238
+ bookings.set(locator, { flights: flights > 0 ? flights : 1, tickets: [] });
239
+ return json(200, { timestamp: now().toISOString().slice(0, 19), confirmationId: locator, request });
240
+ }
241
+ case "getBooking": {
242
+ const locator = String(request.confirmationId ?? "");
243
+ record("getBooking", locator);
244
+ const booking = bookings.get(locator);
245
+ return booking === void 0 ? notFound() : json(200, bookingJson(locator, booking));
246
+ }
247
+ case "cancelBooking":
248
+ return cancel(request);
249
+ case "fulfillFlightTickets":
250
+ return fulfill(request);
251
+ default:
252
+ return json(404, { errorCode: "ERR.2SG.NOT_FOUND", message: `sabre-client/testing: ${path} no est\xE1 simulado` });
253
+ }
254
+ };
255
+ }
256
+
4
257
  // src/queue/queue-access.ts
5
258
  import { XMLParser as XMLParser2 } from "fast-xml-parser";
6
259
 
@@ -438,7 +691,7 @@ function signatureLocator(lines) {
438
691
  var SWS_NAMESPACE = "http://webservices.sabre.com/sabreXML/2011/10";
439
692
  var STL_NAMESPACE = "http://services.sabre.com/STL/v01";
440
693
  var SIGNATURE_DATE = "1200/01JAN26";
441
- var LOCATOR = /^[A-Z0-9]{6}$/;
694
+ var LOCATOR2 = /^[A-Z0-9]{6}$/;
442
695
  var NAVIGATION = { I: "ignore", QR: "remove", QXI: "exit" };
443
696
  var QUEUE_FAKE_SELECTION_FUNCTIONS = ["QD", "AK", "AL", "PI", "AC"];
444
697
  var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
@@ -449,7 +702,7 @@ function placedOn(placedAt, criteria) {
449
702
  if (date === null || placed === null) return false;
450
703
  return Number(placed[1]) === MONTHS2.indexOf(date[2]) + 1 && Number(placed[2]) === Number(date[1]);
451
704
  }
452
- function fail(message) {
705
+ function fail2(message) {
453
706
  throw new TypeError(`sabre-client/testing: ${message}`);
454
707
  }
455
708
  var parser2 = new XMLParser3({
@@ -502,11 +755,11 @@ function keyAttribute(key, pad2 = false) {
502
755
  function validateKey(key) {
503
756
  if (/^\d+$/.test(key)) {
504
757
  const number = Number(key);
505
- if (number < MIN_QUEUE_NUMBER || number > MAX_QUEUE_NUMBER) fail(`la cola ${key} est\xE1 fuera de 0\u2013511`);
758
+ if (number < MIN_QUEUE_NUMBER || number > MAX_QUEUE_NUMBER) fail2(`la cola ${key} est\xE1 fuera de 0\u2013511`);
506
759
  return;
507
760
  }
508
761
  if (key.startsWith(NAME_PREFIX) ? key.slice(NAME_PREFIX.length).trim() === "" : key.trim() === "") {
509
- fail("el nombre de una cola no puede estar vac\xEDo");
762
+ fail2("el nombre de una cola no puede estar vac\xEDo");
510
763
  }
511
764
  }
512
765
  function shortDate(date) {
@@ -518,26 +771,26 @@ function longDate(date) {
518
771
  function toMessage(item, index) {
519
772
  const label = `queues['${MESSAGE_QUEUE}'][${index}]`;
520
773
  if (!Array.isArray(item) || item.some((line) => typeof line !== "string")) {
521
- fail(`${label}: la S es la cola de mensajes; cada elemento es el texto de un mensaje (string[])`);
774
+ fail2(`${label}: la S es la cola de mensajes; cada elemento es el texto de un mensaje (string[])`);
522
775
  }
523
776
  const lines = item;
524
- if (!lines.some((line) => line.trim() !== "")) fail(`${label}: el mensaje no puede estar vac\xEDo`);
777
+ if (!lines.some((line) => line.trim() !== "")) fail2(`${label}: el mensaje no puede estar vac\xEDo`);
525
778
  return [...lines];
526
779
  }
527
780
  function toPnr(item, label) {
528
781
  if (Array.isArray(item)) {
529
782
  const lines2 = item;
530
783
  const locator = signatureLocator(lines2);
531
- if (locator === void 0) fail(`${label}: el p\xE1rrafo no tiene l\xEDnea de firma; pasa { locator, paragraph }`);
784
+ if (locator === void 0) fail2(`${label}: el p\xE1rrafo no tiene l\xEDnea de firma; pasa { locator, paragraph }`);
532
785
  return { locator, paragraph: [...lines2], primaryPassenger: void 0, placedAt: void 0, placedBy: void 0 };
533
786
  }
534
787
  const pnr = item;
535
- if (typeof pnr !== "object" || pnr === null || typeof pnr.locator !== "string" || !LOCATOR.test(pnr.locator)) {
536
- fail(`${label}: locator debe ser de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
788
+ if (typeof pnr !== "object" || pnr === null || typeof pnr.locator !== "string" || !LOCATOR2.test(pnr.locator)) {
789
+ fail2(`${label}: locator debe ser de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
537
790
  }
538
791
  const lines = [...pnr.paragraph ?? []];
539
792
  const signed = signatureLocator(lines);
540
- if (signed !== void 0 && signed !== pnr.locator) fail(`${label}: la l\xEDnea de firma es de ${signed}, no de ${pnr.locator}`);
793
+ if (signed !== void 0 && signed !== pnr.locator) fail2(`${label}: la l\xEDnea de firma es de ${signed}, no de ${pnr.locator}`);
541
794
  if (signed === void 0) lines.push(`XXXX.XXXX*AAA ${SIGNATURE_DATE} ${pnr.locator} H`);
542
795
  return {
543
796
  locator: pnr.locator,
@@ -588,22 +841,22 @@ var ACTIVITY_COUNTERS = [
588
841
  ["unableToReach", "UnableToReach"]
589
842
  ];
590
843
  function toActivity(value, label) {
591
- if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`${label} debe ser un objeto de contadores`);
844
+ if (typeof value !== "object" || value === null || Array.isArray(value)) fail2(`${label} debe ser un objeto de contadores`);
592
845
  const known = new Set(ACTIVITY_COUNTERS.map(([key]) => key));
593
846
  const result = {};
594
847
  for (const [key, count] of Object.entries(value)) {
595
- if (key === "numMessages") fail(`${label}.numMessages sale de la cola del fake: no se siembra`);
596
- if (!known.has(key)) fail(`${label}.${key} no es un contador de queue.activity`);
597
- if (!Number.isInteger(count) || count < 0) fail(`${label}.${key} debe ser un entero de 0 en adelante`);
848
+ if (key === "numMessages") fail2(`${label}.numMessages sale de la cola del fake: no se siembra`);
849
+ if (!known.has(key)) fail2(`${label}.${key} no es un contador de queue.activity`);
850
+ if (!Number.isInteger(count) || count < 0) fail2(`${label}.${key} debe ser un entero de 0 en adelante`);
598
851
  result[key] = count;
599
852
  }
600
853
  return result;
601
854
  }
602
855
  function toHistory(value, label) {
603
- if (!Array.isArray(value)) fail(`${label} debe ser una lista de transacciones`);
856
+ if (!Array.isArray(value)) fail2(`${label} debe ser una lista de transacciones`);
604
857
  return value.map((transaction, index) => {
605
858
  const at = `${label}[${index}]`;
606
- if (typeof transaction !== "object" || transaction === null) fail(`${at} debe ser un objeto`);
859
+ if (typeof transaction !== "object" || transaction === null) fail2(`${at} debe ser un objeto`);
607
860
  const t = transaction;
608
861
  for (const [list, name] of [
609
862
  [t.flightSegments, "flightSegments"],
@@ -611,12 +864,12 @@ function toHistory(value, label) {
611
864
  [t.remarks, "remarks"]
612
865
  ]) {
613
866
  if (list === void 0) continue;
614
- if (!Array.isArray(list)) fail(`${at}.${name} debe ser una lista`);
867
+ if (!Array.isArray(list)) fail2(`${at}.${name} debe ser una lista`);
615
868
  list.forEach((entry, position) => {
616
- if (typeof entry?.action !== "string" || entry.action.trim() === "") fail(`${at}.${name}[${position}].action debe ser un string no vac\xEDo`);
869
+ if (typeof entry?.action !== "string" || entry.action.trim() === "") fail2(`${at}.${name}[${position}].action debe ser un string no vac\xEDo`);
617
870
  });
618
871
  }
619
- if (t.accounting !== void 0 && !Array.isArray(t.accounting)) fail(`${at}.accounting debe ser una lista`);
872
+ if (t.accounting !== void 0 && !Array.isArray(t.accounting)) fail2(`${at}.accounting debe ser una lista`);
620
873
  return t;
621
874
  });
622
875
  }
@@ -659,11 +912,57 @@ function historicalInfo(t) {
659
912
  "</HistoricalInfo>"
660
913
  ].join("");
661
914
  }
915
+ var DAILY_SALES_DATE = /^\d{4}-\d{2}-\d{2}$/;
916
+ function toDailySalesDay(value, label) {
917
+ const day = Array.isArray(value) ? { transactions: value } : value;
918
+ if (typeof day !== "object" || day === null || !Array.isArray(day.transactions)) fail2(`${label} debe ser una lista de transacciones o { transactions }`);
919
+ if (day.status !== void 0 && !["complete", "incomplete", "deferred"].includes(day.status)) fail2(`${label}.status debe ser complete, incomplete o deferred`);
920
+ for (const [index, t] of day.transactions.entries()) {
921
+ const at = `${label}.transactions[${index}]`;
922
+ if (typeof t !== "object" || t === null) fail2(`${at} debe ser un objeto`);
923
+ for (const field of ["documentType", "transactionCode", "settlementType", "documentNumber", "agentSine", "transactionDateTime"]) {
924
+ if (typeof t[field] !== "string") fail2(`${at}.${field} debe ser un string`);
925
+ }
926
+ if (typeof t.airline?.code !== "string") fail2(`${at}.airline.code debe ser un string`);
927
+ }
928
+ return day;
929
+ }
930
+ function transactionXml(t) {
931
+ const el = (name, value) => value === void 0 ? "" : `<asr:${name}>${escape(value)}</asr:${name}>`;
932
+ const payments = t.payments === void 0 ? "" : `<asr:Payments>${el("CurrencyCode", t.payments.currencyCode)}<asr:DocumentPayment>${el("PaymentTotal", t.payments.total)}${t.payments.details.map((d) => `<asr:PaymentDetail${attrs([["fop", d.fop], ["category", d.category], ["cardType", d.cardType]])}>${el("PaymentAmount", d.amount)}</asr:PaymentDetail>`).join("")}</asr:DocumentPayment></asr:Payments>`;
933
+ const flags = attrs([
934
+ ["nonInteractive", t.nonInteractive === void 0 ? void 0 : String(t.nonInteractive)],
935
+ ["electronicDocument", t.electronicDocument === void 0 ? void 0 : String(t.electronicDocument)],
936
+ ["itineraryType", t.itineraryType],
937
+ ["automatedRefund", t.automatedRefund === void 0 ? void 0 : String(t.automatedRefund)]
938
+ ]);
939
+ return [
940
+ `<asr:Transaction${flags}>`,
941
+ el("DocumentType", t.documentType),
942
+ el("SettlementType", t.settlementType),
943
+ el("PnrLocator", t.pnrLocator),
944
+ el("PassengerName", t.passengerName),
945
+ "<asr:DocumentDetails>",
946
+ el("DocumentNumber", t.documentNumber),
947
+ el("LastConjunctiveDocumentNumber", t.lastConjunctiveDocumentNumber),
948
+ el("StockType", t.stockType),
949
+ el("DocumentStatusCode", t.documentStatusCode),
950
+ el("TransactionCode", t.transactionCode),
951
+ "</asr:DocumentDetails>",
952
+ `<asr:AirlineCode${attrs([["AirlineNumber", t.airline.number]])}>${escape(t.airline.code)}</asr:AirlineCode>`,
953
+ t.commission === void 0 ? "" : `<asr:Commission>${el("Amount", t.commission.amount)}${el("Percent", t.commission.percent)}</asr:Commission>`,
954
+ payments,
955
+ el("AgentSine", t.agentSine),
956
+ el("AgentPseudoCity", t.agentPseudoCity),
957
+ el("TransactionDateTime", t.transactionDateTime),
958
+ "</asr:Transaction>"
959
+ ].join("");
960
+ }
662
961
  var NOT_ALLOWED_INSIDE_QUEUE = "NAK6 -NOT ALLOWED- FINISH OR IGNORE OTHER ACTIVITY";
663
962
  function createQueueFake(options = {}) {
664
- if (typeof options !== "object" || options === null) fail("options debe ser un objeto");
963
+ if (typeof options !== "object" || options === null) fail2("options debe ser un objeto");
665
964
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
666
- if (typeof now !== "function") fail("now debe ser una funci\xF3n");
965
+ if (typeof now !== "function") fail2("now debe ser una funci\xF3n");
667
966
  const pnrs = /* @__PURE__ */ new Map();
668
967
  const queues = /* @__PURE__ */ new Map();
669
968
  const sessions = /* @__PURE__ */ new Map();
@@ -673,7 +972,7 @@ function createQueueFake(options = {}) {
673
972
  const pnr = toPnr(item, label);
674
973
  const known = pnrs.get(pnr.locator);
675
974
  if (known !== void 0 && JSON.stringify(known.paragraph) !== JSON.stringify(pnr.paragraph)) {
676
- fail(`${label}: el PNR ${pnr.locator} aparece con dos p\xE1rrafos distintos`);
975
+ fail2(`${label}: el PNR ${pnr.locator} aparece con dos p\xE1rrafos distintos`);
677
976
  }
678
977
  if (known === void 0) {
679
978
  pnrs.set(pnr.locator, { locator: pnr.locator, paragraph: pnr.paragraph, primaryPassenger: pnr.primaryPassenger });
@@ -683,7 +982,7 @@ function createQueueFake(options = {}) {
683
982
  for (const [optionName, items] of Object.entries(options.queues ?? {})) {
684
983
  const key = optionKey(optionName);
685
984
  validateKey(key);
686
- if (!Array.isArray(items)) fail(`queues['${key}'] debe ser una lista`);
985
+ if (!Array.isArray(items)) fail2(`queues['${key}'] debe ser una lista`);
687
986
  if (key === MESSAGE_QUEUE) {
688
987
  queues.set(key, items.map((item, index) => ({ locator: "", placedAt: void 0, placedBy: void 0, message: toMessage(item, index) })));
689
988
  continue;
@@ -703,9 +1002,25 @@ function createQueueFake(options = {}) {
703
1002
  validateKey(key);
704
1003
  activity.set(key, toActivity(counters, `activity['${key}']`));
705
1004
  }
1005
+ const dailySalesTimeZone = options.dailySalesTimeZone ?? "UTC";
1006
+ let dailySalesDay;
1007
+ try {
1008
+ dailySalesDay = new Intl.DateTimeFormat("en-CA", { timeZone: dailySalesTimeZone, year: "numeric", month: "2-digit", day: "2-digit" });
1009
+ } catch {
1010
+ fail2(`dailySalesTimeZone: '${dailySalesTimeZone}' no es una zona IANA v\xE1lida`);
1011
+ }
1012
+ const dailySales = /* @__PURE__ */ new Map();
1013
+ for (const [pccKey, days] of Object.entries(options.dailySales ?? {})) {
1014
+ if (!/^[A-Z0-9]{3,4}$/.test(pccKey)) fail2(`dailySales['${pccKey}']: la clave debe ser un PCC de 3 o 4 letras may\xFAsculas o d\xEDgitos`);
1015
+ if (typeof days !== "object" || days === null) fail2(`dailySales['${pccKey}'] debe ser un objeto con las fechas`);
1016
+ for (const [date, day] of Object.entries(days)) {
1017
+ if (!DAILY_SALES_DATE.test(date)) fail2(`dailySales['${pccKey}']['${date}']: la fecha debe tener la forma 'YYYY-MM-DD'`);
1018
+ dailySales.set(`${pccKey} ${date}`, toDailySalesDay(day, `dailySales['${pccKey}']['${date}']`));
1019
+ }
1020
+ }
706
1021
  const histories = /* @__PURE__ */ new Map();
707
1022
  for (const [locator, transactions] of Object.entries(options.histories ?? {})) {
708
- if (!LOCATOR.test(locator)) fail(`histories['${locator}']: la clave debe ser un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
1023
+ if (!LOCATOR2.test(locator)) fail2(`histories['${locator}']: la clave debe ser un localizador de 6 caracteres alfanum\xE9ricos en may\xFAscula`);
709
1024
  histories.set(locator, toHistory(transactions, `histories['${locator}']`));
710
1025
  }
711
1026
  const queue = (key) => {
@@ -994,6 +1309,40 @@ function createQueueFake(options = {}) {
994
1309
  const created = { rph: 1, createdAt: longDate(now()), agent: "AAA", pcc: "XXXX", receivedFrom: "SABRE-CLIENT/TESTING" };
995
1310
  return historyRS(`${results("Complete", success)}${historicalInfo(created)}`);
996
1311
  };
1312
+ const dailySalesSummary = (rq, sessionPcc) => {
1313
+ const criteria = node2(rq.SelectionCriteria) ?? {};
1314
+ const requestedPcc = text(criteria.PseudoCityCode);
1315
+ const requestedDate = text(criteria.ReportDate);
1316
+ calls.push({ command: "dailySalesSummary", ...requestedPcc === void 0 ? {} : { pcc: requestedPcc }, ...requestedDate === void 0 ? {} : { date: requestedDate } });
1317
+ const pcc = requestedPcc ?? sessionPcc;
1318
+ const date = requestedDate ?? dailySalesDay.format(now());
1319
+ const day = dailySales.get(`${pcc} ${date}`) ?? { transactions: [] };
1320
+ const namespaces = 'xmlns:asr="http://www.sabre.com/ns/Ticketing/AsrServices/1.0" xmlns:stl="http://services.sabre.com/STL/v01"';
1321
+ const resultsXml = (inner) => `<asr:Header><stl:Results>${inner}</stl:Results></asr:Header>`;
1322
+ const errorXml = (status, code, message) => `<stl:Error type="BusinessLogic" status="${escape(status)}"><stl:System>SABRE-CLIENT-TESTING</stl:System><stl:ErrorCode>${escape(code)}</stl:ErrorCode><stl:Message>${escape(message)}</stl:Message></stl:Error>`;
1323
+ if (day.errorCode !== void 0) {
1324
+ return `<asr:DailySalesSummaryRS ${namespaces} version="1.2.2">${resultsXml(errorXml("NotProcessed", day.errorCode, `sabre-client/testing: error ${day.errorCode} forzado`))}</asr:DailySalesSummaryRS>`;
1325
+ }
1326
+ const wanted = text(criteria.DocumentType);
1327
+ const transactions = day.transactions.filter(
1328
+ (t) => wanted === void 0 || t.documentType === wanted || wanted === "RFND" && t.documentType === "RFD"
1329
+ );
1330
+ const deferred = day.status === "deferred" ? { reportId: "FAKE-REPORT", queryStillRunning: true, ...day.deferred } : void 0;
1331
+ const rootAttrs = attrs([
1332
+ ["isQueryStillRunning", String(deferred?.queryStillRunning ?? false)],
1333
+ ["reportId", deferred?.reportId],
1334
+ ["chunkNumber", deferred?.chunkNumber],
1335
+ ["maxChunks", deferred?.maxChunks]
1336
+ ]);
1337
+ const incomplete = day.status === "incomplete" ? errorXml("Incomplete", "0", `The following are the failed transactions : [${(day.failedDocuments ?? []).join(", ")}]`) : "";
1338
+ return [
1339
+ `<asr:DailySalesSummaryRS ${namespaces} version="1.2.2"${rootAttrs}>`,
1340
+ resultsXml(`<stl:Success><stl:System>SABRE-CLIENT-TESTING</stl:System></stl:Success>${incomplete}`),
1341
+ `<asr:DSHeader><asr:PseudoCityCode>${escape(pcc)}</asr:PseudoCityCode>${day.agencyName === void 0 ? "" : `<asr:AgencyName>${escape(day.agencyName)}</asr:AgencyName>`}</asr:DSHeader>`,
1342
+ `<asr:DailySalesReport><asr:ReportDate>${escape(date)}</asr:ReportDate>${transactions.map(transactionXml).join("")}</asr:DailySalesReport>`,
1343
+ "</asr:DailySalesSummaryRS>"
1344
+ ].join("");
1345
+ };
997
1346
  const handle = (raw) => {
998
1347
  let document;
999
1348
  try {
@@ -1053,6 +1402,8 @@ function createQueueFake(options = {}) {
1053
1402
  return reply("QueueAnalysisLLSRS", session, token, analyze(session, rq));
1054
1403
  case "TravelItineraryHistoryLLSRQ":
1055
1404
  return reply("TravelItineraryHistoryLLSRS", session, token, history(session, rq));
1405
+ case "TKT_TravelAgencyReportsRQ":
1406
+ return reply("TKT_TravelAgencyReportsRS", session, token, dailySalesSummary(rq, text(messageHeader?.CPAId) ?? ""));
1056
1407
  default:
1057
1408
  return {
1058
1409
  status: 500,
@@ -1065,10 +1416,20 @@ function createQueueFake(options = {}) {
1065
1416
  };
1066
1417
  }
1067
1418
  };
1419
+ const rest = createBookingHandler(
1420
+ { ...options.bookings === void 0 ? {} : { bookings: options.bookings }, ...options.ticketing === void 0 ? {} : { ticketing: options.ticketing } },
1421
+ now,
1422
+ (command, locator) => calls.push({ command, ...locator === void 0 ? {} : { locator } })
1423
+ );
1068
1424
  const fakeFetch = async (input, init) => {
1069
1425
  const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
1070
1426
  signal?.throwIfAborted();
1071
1427
  const raw = typeof init?.body === "string" ? init.body : input instanceof Request ? await input.text() : String(init?.body ?? "");
1428
+ const url = input instanceof Request ? input.url : String(input);
1429
+ const restReply = rest(url, raw);
1430
+ if (restReply === "transport") throw new TypeError("fetch failed (sabre-client/testing: fallo de red forzado)");
1431
+ if (restReply === "abort") throw new DOMException("The operation was aborted. (sabre-client/testing: abort forzado)", "AbortError");
1432
+ if (restReply !== void 0) return restReply;
1072
1433
  const { status, body } = handle(raw);
1073
1434
  return new Response(body, { status, headers: { "Content-Type": "text/xml;charset=UTF-8" } });
1074
1435
  };
@@ -3665,11 +4026,11 @@ var queue_production_walk_default = {
3665
4026
  };
3666
4027
 
3667
4028
  // src/testing/fixtures.ts
3668
- function fixture(json) {
4029
+ function fixture(json2) {
3669
4030
  return Object.freeze({
3670
- recordedAt: json.recordedAt,
4031
+ recordedAt: json2.recordedAt,
3671
4032
  responses: Object.freeze(
3672
- json.responses.map(({ operation, status, body }) => Object.freeze({ operation, status, body }))
4033
+ json2.responses.map(({ operation, status, body }) => Object.freeze({ operation, status, body }))
3673
4034
  )
3674
4035
  });
3675
4036
  }