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/README.es.md +226 -12
- package/README.md +227 -12
- package/dist/index.d.ts +297 -4
- package/dist/index.js +707 -165
- package/dist/index.js.map +1 -1
- package/dist/{reservation-history-C6giSdFd.d.ts → reservation-history-BRKQBHsj.d.ts} +150 -1
- package/dist/testing.d.ts +116 -5
- package/dist/testing.js +388 -27
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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() !== ""))
|
|
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)
|
|
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" || !
|
|
536
|
-
|
|
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)
|
|
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))
|
|
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")
|
|
596
|
-
if (!known.has(key))
|
|
597
|
-
if (!Number.isInteger(count) || count < 0)
|
|
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))
|
|
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)
|
|
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))
|
|
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() === "")
|
|
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))
|
|
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)
|
|
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")
|
|
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
|
-
|
|
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))
|
|
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 (!
|
|
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(
|
|
4029
|
+
function fixture(json2) {
|
|
3669
4030
|
return Object.freeze({
|
|
3670
|
-
recordedAt:
|
|
4031
|
+
recordedAt: json2.recordedAt,
|
|
3671
4032
|
responses: Object.freeze(
|
|
3672
|
-
|
|
4033
|
+
json2.responses.map(({ operation, status, body }) => Object.freeze({ operation, status, body }))
|
|
3673
4034
|
)
|
|
3674
4035
|
});
|
|
3675
4036
|
}
|