globalpayments-api 3.10.15 → 3.11.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 (115) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/appsec.properties +1 -1
  3. package/lib/src/Builders/BaseBuilder/ValidationClause.d.ts +3 -2
  4. package/lib/src/Builders/BaseBuilder/ValidationClause.js +23 -15
  5. package/lib/src/Builders/BaseBuilder/ValidationClause.js.map +1 -1
  6. package/lib/src/Builders/BaseBuilder/Validations.js +7 -2
  7. package/lib/src/Builders/BaseBuilder/Validations.js.map +1 -1
  8. package/lib/src/Builders/RequestBuilder/GpEcom/GpEcomManagementRequestBuilder.js +2 -2
  9. package/lib/src/Builders/RequestBuilder/GpEcom/GpEcomManagementRequestBuilder.js.map +1 -1
  10. package/lib/src/Entities/Enums.d.ts +4 -2
  11. package/lib/src/Entities/Enums.js +3 -1
  12. package/lib/src/Entities/Enums.js.map +1 -1
  13. package/lib/src/Entities/RecurringEntity.js +2 -2
  14. package/lib/src/Entities/RecurringEntity.js.map +1 -1
  15. package/lib/src/Entities/UPA/ProcessIndicator.d.ts +1 -1
  16. package/lib/src/Entities/UPA/ProcessIndicator.js.map +1 -1
  17. package/lib/src/Entities/UPA/UpaParam.d.ts +7 -7
  18. package/lib/src/Entities/UPA/UpaParam.js.map +1 -1
  19. package/lib/src/Entities/UPA/UpaTransactionData.d.ts +5 -5
  20. package/lib/src/Entities/UPA/UpaTransactionData.js.map +1 -1
  21. package/lib/src/Entities/UPA/index.d.ts +3 -0
  22. package/lib/src/Entities/UPA/index.js +3 -0
  23. package/lib/src/Entities/UPA/index.js.map +1 -1
  24. package/lib/src/Gateways/GpApiConnector.js +2 -2
  25. package/lib/src/Gateways/GpApiConnector.js.map +1 -1
  26. package/lib/src/Gateways/PorticoConnector.d.ts +1 -1
  27. package/lib/src/Terminals/Abstractions/IDeviceInterface.d.ts +23 -2
  28. package/lib/src/Terminals/Abstractions/IDeviceResponse.d.ts +71 -24
  29. package/lib/src/Terminals/Builders/TerminalAuthBuilder.d.ts +54 -22
  30. package/lib/src/Terminals/Builders/TerminalAuthBuilder.js +98 -13
  31. package/lib/src/Terminals/Builders/TerminalAuthBuilder.js.map +1 -1
  32. package/lib/src/Terminals/Builders/TerminalBuilder.d.ts +6 -3
  33. package/lib/src/Terminals/Builders/TerminalBuilder.js +9 -0
  34. package/lib/src/Terminals/Builders/TerminalBuilder.js.map +1 -1
  35. package/lib/src/Terminals/Builders/TerminalManageBuilder.d.ts +16 -6
  36. package/lib/src/Terminals/Builders/TerminalManageBuilder.js +27 -2
  37. package/lib/src/Terminals/Builders/TerminalManageBuilder.js.map +1 -1
  38. package/lib/src/Terminals/Builders/TerminalReportBuilder.d.ts +2 -0
  39. package/lib/src/Terminals/Builders/TerminalReportBuilder.js +8 -0
  40. package/lib/src/Terminals/Builders/TerminalReportBuilder.js.map +1 -1
  41. package/lib/src/Terminals/Builders/index.d.ts +1 -0
  42. package/lib/src/Terminals/Builders/index.js +1 -0
  43. package/lib/src/Terminals/Builders/index.js.map +1 -1
  44. package/lib/src/Terminals/DeviceController.d.ts +6 -5
  45. package/lib/src/Terminals/DeviceController.js.map +1 -1
  46. package/lib/src/Terminals/DeviceInterface.d.ts +23 -3
  47. package/lib/src/Terminals/DeviceInterface.js +68 -0
  48. package/lib/src/Terminals/DeviceInterface.js.map +1 -1
  49. package/lib/src/Terminals/Enums.d.ts +11 -0
  50. package/lib/src/Terminals/Enums.js +14 -1
  51. package/lib/src/Terminals/Enums.js.map +1 -1
  52. package/lib/src/Terminals/TerminalResponse.d.ts +44 -36
  53. package/lib/src/Terminals/TerminalResponse.js +8 -0
  54. package/lib/src/Terminals/TerminalResponse.js.map +1 -1
  55. package/lib/src/Terminals/UPA/Entities/UpaMessageId.d.ts +10 -1
  56. package/lib/src/Terminals/UPA/Entities/UpaMessageId.js +9 -0
  57. package/lib/src/Terminals/UPA/Entities/UpaMessageId.js.map +1 -1
  58. package/lib/src/Terminals/UPA/Entities/UpaSearchCriteria.d.ts +3 -1
  59. package/lib/src/Terminals/UPA/Entities/UpaSearchCriteria.js +2 -0
  60. package/lib/src/Terminals/UPA/Entities/UpaSearchCriteria.js.map +1 -1
  61. package/lib/src/Terminals/UPA/Entities/index.d.ts +1 -0
  62. package/lib/src/Terminals/UPA/Entities/index.js +1 -0
  63. package/lib/src/Terminals/UPA/Entities/index.js.map +1 -1
  64. package/lib/src/Terminals/UPA/Interfaces/UpaMicInterface.d.ts +1 -1
  65. package/lib/src/Terminals/UPA/Interfaces/UpaMicInterface.js +7 -5
  66. package/lib/src/Terminals/UPA/Interfaces/UpaMicInterface.js.map +1 -1
  67. package/lib/src/Terminals/UPA/Reponses/BatchList.d.ts +1 -0
  68. package/lib/src/Terminals/UPA/Reponses/BatchList.js +2 -0
  69. package/lib/src/Terminals/UPA/Reponses/BatchList.js.map +1 -1
  70. package/lib/src/Terminals/UPA/Reponses/BatchReportResponse.d.ts +1 -0
  71. package/lib/src/Terminals/UPA/Reponses/BatchReportResponse.js +2 -0
  72. package/lib/src/Terminals/UPA/Reponses/BatchReportResponse.js.map +1 -1
  73. package/lib/src/Terminals/UPA/Reponses/OpenTabDetailsResponse.d.ts +2 -0
  74. package/lib/src/Terminals/UPA/Reponses/OpenTabDetailsResponse.js +4 -0
  75. package/lib/src/Terminals/UPA/Reponses/OpenTabDetailsResponse.js.map +1 -1
  76. package/lib/src/Terminals/UPA/Reponses/SafReportResponse.d.ts +3 -0
  77. package/lib/src/Terminals/UPA/Reponses/SafReportResponse.js +6 -0
  78. package/lib/src/Terminals/UPA/Reponses/SafReportResponse.js.map +1 -1
  79. package/lib/src/Terminals/UPA/Reponses/TransactionResponse.d.ts +48 -25
  80. package/lib/src/Terminals/UPA/Reponses/TransactionResponse.js +151 -15
  81. package/lib/src/Terminals/UPA/Reponses/TransactionResponse.js.map +1 -1
  82. package/lib/src/Terminals/UPA/Reponses/UpaSAFResponse.js +3 -2
  83. package/lib/src/Terminals/UPA/Reponses/UpaSAFResponse.js.map +1 -1
  84. package/lib/src/Terminals/UPA/Reponses/index.d.ts +8 -0
  85. package/lib/src/Terminals/UPA/Reponses/index.js +8 -0
  86. package/lib/src/Terminals/UPA/Reponses/index.js.map +1 -1
  87. package/lib/src/Terminals/UPA/UpaController.d.ts +17 -5
  88. package/lib/src/Terminals/UPA/UpaController.js +338 -79
  89. package/lib/src/Terminals/UPA/UpaController.js.map +1 -1
  90. package/lib/src/Terminals/UPA/UpaInterface.d.ts +26 -3
  91. package/lib/src/Terminals/UPA/UpaInterface.js +311 -12
  92. package/lib/src/Terminals/UPA/UpaInterface.js.map +1 -1
  93. package/lib/src/Terminals/index.d.ts +2 -0
  94. package/lib/src/Terminals/index.js +2 -0
  95. package/lib/src/Terminals/index.js.map +1 -1
  96. package/lib/src/Utils/StringUtils.js +1 -1
  97. package/lib/src/Utils/StringUtils.js.map +1 -1
  98. package/lib/test/Integration/Gateways/CI/upa_mitc/UpaFunctionalityTests.test.js +19 -8
  99. package/lib/test/Integration/Gateways/CI/upa_mitc/UpaFunctionalityTests.test.js.map +1 -1
  100. package/lib/test/Integration/Gateways/Terminals/RequestIdProvider.d.ts +15 -1
  101. package/lib/test/Integration/Gateways/Terminals/RequestIdProvider.js +88 -3
  102. package/lib/test/Integration/Gateways/Terminals/RequestIdProvider.js.map +1 -1
  103. package/lib/test/Unit/Terminals/UPA/UpaAdmin.test.d.ts +1 -0
  104. package/lib/test/Unit/Terminals/UPA/UpaAdmin.test.js +1260 -0
  105. package/lib/test/Unit/Terminals/UPA/UpaAdmin.test.js.map +1 -0
  106. package/lib/test/Unit/Terminals/UPA/UpaCredit.test.d.ts +1 -0
  107. package/lib/test/Unit/Terminals/UPA/UpaCredit.test.js +411 -0
  108. package/lib/test/Unit/Terminals/UPA/UpaCredit.test.js.map +1 -0
  109. package/lib/test/Unit/Terminals/UPA/UpaHelpertest.d.ts +55 -0
  110. package/lib/test/Unit/Terminals/UPA/UpaHelpertest.js +490 -0
  111. package/lib/test/Unit/Terminals/UPA/UpaHelpertest.js.map +1 -0
  112. package/lib/test/Unit/Terminals/UPA/UpaReport.test.d.ts +1 -0
  113. package/lib/test/Unit/Terminals/UPA/UpaReport.test.js +801 -0
  114. package/lib/test/Unit/Terminals/UPA/UpaReport.test.js.map +1 -0
  115. package/package.json +2 -2
@@ -0,0 +1,801 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Unit tests for UPA Report methods:
5
+ * getSAFReport(), findBatches(), getBatchReport(), getBatchDetails(),
6
+ * getOpenTabDetails()
7
+ *
8
+ * - GetSafReport
9
+ * - GetBatchReport
10
+ * - GetBatchDetails
11
+ * - GetOpenTabDetails
12
+ * - FindBatches
13
+ */
14
+ const src_1 = require("../../../../src");
15
+ const Enums_1 = require("../../../../src/Terminals/Enums");
16
+ const TerminalReportBuilder_1 = require("../../../../src/Terminals/Builders/TerminalReportBuilder");
17
+ const UpaSearchCriteria_1 = require("../../../../src/Terminals/UPA/Entities/UpaSearchCriteria");
18
+ const SafReportResponse_1 = require("../../../../src/Terminals/UPA/Reponses/SafReportResponse");
19
+ const UpaHelpertest_1 = require("./UpaHelpertest");
20
+ jest.setTimeout(240000);
21
+ function sleep(delayMs) {
22
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
23
+ }
24
+ async function settleDevice(delayMs = 8000) {
25
+ if (UpaHelpertest_1.useLiveMic) {
26
+ await sleep(delayMs);
27
+ }
28
+ }
29
+ afterEach(async () => {
30
+ await settleDevice();
31
+ });
32
+ // ===========================================================================
33
+ // getSAFReport()
34
+ // ===========================================================================
35
+ (0, UpaHelpertest_1.describeUpaLive)("UPA Report – getSAFReport()", () => {
36
+ let device;
37
+ beforeEach(() => {
38
+ device = (0, UpaHelpertest_1.createTestDevice)();
39
+ });
40
+ test("[UpaCreditTests] sale() serializes processing indicators without mocked responses", async () => {
41
+ await device.sale(1.0).withEcrId(13).execute();
42
+ // await device.sale(2.50).withEcrId(13).execute();
43
+ // await device.sale(0.50).withEcrId(13).execute(); // small amount → SAF-approved
44
+ await device.sendStoreAndForward();
45
+ });
46
+ test("[UpaReportTests:37] getSAFReport() round-trips over live MITC (either populated buckets or SAF001)", async () => {
47
+ // SAF data only accumulates when the terminal takes/uploads offline txns.
48
+ // On a clean/always-online terminal this returns SAF001 (no records) —
49
+ // the SDK contract must round-trip both outcomes cleanly.
50
+ const response = (await device
51
+ .getSAFReport()
52
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
53
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.ReturnData)
54
+ .execute());
55
+ expect(response).toBeInstanceOf(SafReportResponse_1.SafReportResponse);
56
+ expect(response.command).toBe("GetSAFReport");
57
+ if (response.status === "Failed") {
58
+ // Per spec §12.4.14 the well-formed empty-store failure code is SAF001.
59
+ // APP011 (MUST ACTIVATE DEVICE) is a tenant/device precondition.
60
+ expect(["SAF001", "APP011"]).toContain(response.deviceResponseCode);
61
+ console.warn(`getSAFReport() returned ${response.deviceResponseCode}: no SAF records on this terminal. See the test file's [UpaReportTests:37] success scenario for bucket-level assertions that require seeded SAF data.`);
62
+ return;
63
+ }
64
+ expect(response.status).toBe("Success");
65
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
66
+ });
67
+ test("[UpaReportTests:37][UpaMicTests:363] getSAFReport() executes over live MITC", async () => {
68
+ const report = await device
69
+ .getSAFReport()
70
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
71
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.ReturnData)
72
+ .execute();
73
+ expect((await report).command).toBe("GetSAFReport");
74
+ expect((await report).status).toBe("Success");
75
+ expect(["00", "SUCCESS"]).toContain((await report).deviceResponseCode);
76
+ });
77
+ test("[UpaReportTests:37] getSAFReport() success scenario with SAF transactions available", async () => {
78
+ const response = (await device
79
+ .getSAFReport()
80
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
81
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.ReturnData)
82
+ .execute());
83
+ expect(response).toBeInstanceOf(SafReportResponse_1.SafReportResponse);
84
+ expect(response.command).toBe("GetSAFReport");
85
+ expect(response.status).toBe("Success");
86
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
87
+ expect(response.reportResult).toBeDefined();
88
+ const buckets = [
89
+ response.reportResult?.approved,
90
+ response.reportResult?.pending,
91
+ response.reportResult?.declined,
92
+ ];
93
+ let aggregateCount = 0;
94
+ let aggregateAmount = 0;
95
+ let inspectedSummaries = 0;
96
+ for (const bucket of buckets) {
97
+ if (!bucket) {
98
+ continue;
99
+ }
100
+ for (const summary of Object.values(bucket)) {
101
+ inspectedSummaries += 1;
102
+ // (b) SafCount matches the number of SafRecords in the bucket.
103
+ expect(summary.count).toBe(summary.transactions.length);
104
+ // (c) SafTotal matches the sum of per-record baseAmount values.
105
+ // ±0.01 tolerance for device-side dollar/cent rounding drift.
106
+ const recordSum = summary.transactions.reduce((sum, txn) => sum + (Number(txn.amount) || 0), 0);
107
+ expect(Math.abs((summary.totalAmount ?? 0) - recordSum)).toBeLessThan(0.011);
108
+ aggregateCount += summary.count ?? 0;
109
+ aggregateAmount += summary.totalAmount ?? 0;
110
+ for (const txn of summary.transactions) {
111
+ if (!txn.maskedCardNumber) {
112
+ continue;
113
+ }
114
+ expect(txn.maskedCardNumber.length).toBeGreaterThanOrEqual(13);
115
+ expect(txn.maskedCardNumber.length).toBeLessThanOrEqual(25);
116
+ // At least one masking character (X or *) must appear.
117
+ expect(txn.maskedCardNumber).toMatch(/[X*]/i);
118
+ // Last four positions must be digits (unmasked PAN suffix).
119
+ expect(txn.maskedCardNumber.slice(-4)).toMatch(/^\d{4}$/);
120
+ }
121
+ }
122
+ }
123
+ if (inspectedSummaries === 0) {
124
+ console.warn("getSAFReport() returned Success but no SafDetails buckets. Skipping bucket-level assertions.");
125
+ return;
126
+ }
127
+ // Aggregate totals across all buckets must match the report header.
128
+ expect(response.reportResult?.totalCount).toBe(aggregateCount);
129
+ expect(Math.abs((response.reportResult?.totalAmount ?? 0) - aggregateAmount)).toBeLessThan(0.011);
130
+ });
131
+ test("[UpaReportTests] getSAFReport() builder captures reportOutput = Print", () => {
132
+ const builder = device
133
+ .getSAFReport()
134
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
135
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.Print);
136
+ expect(builder).toBeInstanceOf(TerminalReportBuilder_1.TerminalReportBuilder);
137
+ expect(builder.reportType).toBe(Enums_1.TerminalReportType.GetSAFReport);
138
+ expect(builder.ecrId).toBe("13");
139
+ expect(builder.reportOutput).toBe("Print");
140
+ });
141
+ test("[UpaReportTests] getSAFReport() builder captures reportOutput = Print|ReturnData", () => {
142
+ const combined = `${Enums_1.ReportOutput.Print}|${Enums_1.ReportOutput.ReturnData}`;
143
+ const builder = device
144
+ .getSAFReport()
145
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
146
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, combined);
147
+ expect(builder.reportOutput).toBe("Print|ReturnData");
148
+ expect(builder.reportOutput.split("|")).toEqual(expect.arrayContaining(["Print", "ReturnData"]));
149
+ });
150
+ test("[UpaReportTests] getSAFReport() builder captures background = true", () => {
151
+ const builder = device
152
+ .getSAFReport()
153
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
154
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.ReturnData)
155
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.Background, "true");
156
+ expect(builder).toBeInstanceOf(TerminalReportBuilder_1.TerminalReportBuilder);
157
+ expect(builder.reportType).toBe(Enums_1.TerminalReportType.GetSAFReport);
158
+ expect(builder.ecrId).toBe("13");
159
+ expect(builder.reportOutput).toBe("ReturnData");
160
+ expect(builder.background).toBe("true");
161
+ });
162
+ test("[UpaReportTests] getSAFReport() with background = true executes over live MITC", async () => {
163
+ const response = await device
164
+ .getSAFReport()
165
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
166
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.ReturnData)
167
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.Background, "true")
168
+ .execute();
169
+ expect(response).toBeInstanceOf(SafReportResponse_1.SafReportResponse);
170
+ expect(response.command).toBe("GetSAFReport");
171
+ if (response.status === "Failed") {
172
+ // SAF001 – no SAF records available
173
+ // APP011 – MUST ACTIVATE DEVICE (tenant/device precondition)
174
+ expect(["SAF001", "APP011"]).toContain(response.deviceResponseCode);
175
+ return;
176
+ }
177
+ (0, UpaHelpertest_1.expectLiveSuccess)(response, "GetSAFReport");
178
+ });
179
+ });
180
+ // ===========================================================================
181
+ // findBatches()
182
+ // ===========================================================================
183
+ (0, UpaHelpertest_1.describeUpaLive)("UPA Report – findBatches()", () => {
184
+ let device;
185
+ beforeEach(() => {
186
+ device = (0, UpaHelpertest_1.createTestDevice)();
187
+ });
188
+ /**
189
+ */
190
+ test("[UpaReportTests:122] findBatches() executes over live MITC", async () => {
191
+ let response = await (0, UpaHelpertest_1.findLiveBatches)(device);
192
+ if (response.batches.length === 0) {
193
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
194
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
195
+ await settleDevice(5000);
196
+ response = await (0, UpaHelpertest_1.findLiveBatches)(device);
197
+ }
198
+ expect(response.command).toBe("AvailableBatches");
199
+ expect(response.status).toBe("Success");
200
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
201
+ });
202
+ test("[UpaReportTests:122] findBatches() batches returned in descending settlement order", async () => {
203
+ let response = await (0, UpaHelpertest_1.findLiveBatches)(device);
204
+ if (response.batches.length === 0) {
205
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
206
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
207
+ await settleDevice(5000);
208
+ response = await (0, UpaHelpertest_1.findLiveBatches)(device);
209
+ }
210
+ expect(response.command).toBe("AvailableBatches");
211
+ expect(response.status).toBe("Success");
212
+ if (response.batches.length < 2) {
213
+ console.warn(`findBatches() DESC-order assertion needs >=2 batches; device returned ${response.batches.length}. Test passes by vacuous truth.`);
214
+ return;
215
+ }
216
+ const sortedDesc = [...response.batches].sort((a, b) => b - a);
217
+ expect(response.batches).toEqual(sortedDesc);
218
+ // First element MUST be the maximum (most recent).
219
+ expect(response.batches[0]).toBe(Math.max(...response.batches));
220
+ for (let i = 1; i < response.batches.length; i += 1) {
221
+ expect(response.batches[i - 1]).toBeGreaterThanOrEqual(response.batches[i]);
222
+ }
223
+ });
224
+ test("[UpaReportTests:122] findBatches() surfaces deviceSerialNumber", async () => {
225
+ const response = await (0, UpaHelpertest_1.findLiveBatches)(device);
226
+ expect(response.command).toBe("AvailableBatches");
227
+ expect(response.status).toBe("Success");
228
+ // deviceSerialNumber is always a string, never undefined.
229
+ expect(typeof response.deviceSerialNumber).toBe("string");
230
+ if (response.deviceSerialNumber === "") {
231
+ console.warn("findBatches() deviceSerialNumber value is empty in response");
232
+ return;
233
+ }
234
+ expect(response.deviceSerialNumber.length).toBeGreaterThanOrEqual(1);
235
+ expect(response.deviceSerialNumber.length).toBeLessThanOrEqual(20);
236
+ expect(response.deviceSerialNumber).toMatch(/^[A-Za-z0-9]+$/);
237
+ });
238
+ test("[UpaReportTests:122] findBatches() surfaces BATCH002 (NO BATCH AVAILABLE) when no batch exists", async () => {
239
+ const response = await (0, UpaHelpertest_1.findLiveBatches)(device);
240
+ if (response.deviceResponseCode === "BATCH002") {
241
+ expect(response.command).toBe("AvailableBatches");
242
+ expect(response.status).toBe("Failed");
243
+ expect(response.deviceResponseText.toUpperCase()).toContain("NO BATCH");
244
+ expect(response.batches).toEqual([]);
245
+ return;
246
+ }
247
+ console.warn(`findBatches() BATCH002 scenario not reproduced: device returned ${response.deviceResponseCode} with ${response.batches.length} batches. BATCH002 requires an empty batch history.`);
248
+ });
249
+ });
250
+ (0, UpaHelpertest_1.describeUpaLive)("UPA Report – BatchList", () => {
251
+ let device;
252
+ beforeEach(() => {
253
+ device = (0, UpaHelpertest_1.createTestDevice)();
254
+ });
255
+ test("BatchList parses batchesAvail in DESC order per §12.4.12.5", async () => {
256
+ let response = await (0, UpaHelpertest_1.findLiveBatches)(device);
257
+ if (response.batches.length === 0) {
258
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
259
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
260
+ await settleDevice(5000);
261
+ response = await (0, UpaHelpertest_1.findLiveBatches)(device);
262
+ }
263
+ expect(response.command).toBe("AvailableBatches");
264
+ expect(response.status).toBe("Success");
265
+ if (response.batches.length < 2) {
266
+ console.warn(`BatchList DESC-order test needs >=2 batches; device returned ${response.batches.length}. Test passes by vacuous truth.`);
267
+ return;
268
+ }
269
+ // §12.4.12.5: batchesAvail is sorted DESC by settlement date/time.
270
+ // Batch numbering on this device is settlement-time-monotonic, so
271
+ // the numeric batchId sequence must be non-increasing (DESC).
272
+ const sortedDesc = [...response.batches].sort((a, b) => b - a);
273
+ expect(response.batches).toEqual(sortedDesc);
274
+ expect(response.batches[0]).toBe(Math.max(...response.batches));
275
+ });
276
+ test("BatchList parses deviceSerialNumber per §12.4.12.5", async () => {
277
+ let response = await (0, UpaHelpertest_1.findLiveBatches)(device);
278
+ if (response.batches.length === 0) {
279
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
280
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
281
+ await settleDevice(5000);
282
+ response = await (0, UpaHelpertest_1.findLiveBatches)(device);
283
+ }
284
+ expect(response.command).toBe("AvailableBatches");
285
+ expect(response.status).toBe("Success");
286
+ // SDK contract: deviceSerialNumber is always a string, never undefined.
287
+ expect(typeof response.deviceSerialNumber).toBe("string");
288
+ if (response.deviceSerialNumber === "") {
289
+ console.warn("BatchList deviceSerialNumber validation skipped: device omitted the optional field in this response. Testing parser contract for non-empty values requires a device response that includes it.");
290
+ return;
291
+ }
292
+ expect(response.deviceSerialNumber.length).toBeGreaterThanOrEqual(1);
293
+ expect(response.deviceSerialNumber.length).toBeLessThanOrEqual(20);
294
+ expect(response.deviceSerialNumber).toMatch(/^[A-Za-z0-9]+$/);
295
+ });
296
+ test("BatchList surfaces empty deviceSerialNumber when device omits the field", async () => {
297
+ let response = await (0, UpaHelpertest_1.findLiveBatches)(device);
298
+ if (response.batches.length === 0) {
299
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
300
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
301
+ await settleDevice(5000);
302
+ response = await (0, UpaHelpertest_1.findLiveBatches)(device);
303
+ }
304
+ expect(response.command).toBe("AvailableBatches");
305
+ expect(response.status).toBe("Success");
306
+ // SDK contract: deviceSerialNumber is always a string, never undefined.
307
+ expect(typeof response.deviceSerialNumber).toBe("string");
308
+ if (response.deviceSerialNumber === "") {
309
+ expect(response.deviceSerialNumber).toBe("");
310
+ return;
311
+ }
312
+ console.warn(`BatchList deviceSerialNumber omission test skipped: device included the field (${response.deviceSerialNumber}) in this response. The parser's null-safety contract is confirmed by the presence check above; omission-handling cannot be tested when the device always populates the field.`);
313
+ });
314
+ });
315
+ // ===========================================================================
316
+ // getBatchReport()
317
+ // ===========================================================================
318
+ (0, UpaHelpertest_1.describeUpaLive)("UPA Report – getBatchReport()", () => {
319
+ let device;
320
+ beforeEach(() => {
321
+ device = (0, UpaHelpertest_1.createTestDevice)();
322
+ });
323
+ test("[UpaReportTests:45][UpaMicTests:378] getBatchReport() executes over live MITC when a batch exists", async () => {
324
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
325
+ if (batches.batches.length === 0) {
326
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
327
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
328
+ await settleDevice(5000);
329
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
330
+ }
331
+ const batchId = batches.batches[0]?.toString();
332
+ if (!batchId) {
333
+ console.warn("getBatchReport() live MITC prerequisite skipped: no batches available.");
334
+ return;
335
+ }
336
+ const response = await device
337
+ .getBatchReport()
338
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, batchId)
339
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
340
+ .execute();
341
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
342
+ // Device may return either "GetBatchReport" or "SendCommand" depending on firmware version
343
+ expect(["GetBatchReport", "SendCommand"]).toContain(response.command);
344
+ expect(response.status).toBe("Success");
345
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
346
+ expect(response.batchRecord).toBeDefined();
347
+ });
348
+ test("[UpaReportTests:45] getBatchReport() without batch parameter returns current batch over live MITC", async () => {
349
+ const response = await device
350
+ .getBatchReport()
351
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
352
+ .execute();
353
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
354
+ expect(response.command).toBe("GetBatchReport");
355
+ if (response.status === "Success") {
356
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
357
+ expect(response.batchRecord).toBeDefined();
358
+ return;
359
+ }
360
+ if (response.deviceResponseCode === "BATCH001") {
361
+ expect(response.status).toBe("Failed");
362
+ expect(response.deviceResponseText.toUpperCase()).toContain("EMPTY BATCH");
363
+ expect(response.batchRecord).toBeUndefined();
364
+ return;
365
+ }
366
+ throw new Error(`getBatchReport() current-batch query returned unexpected code=${response.deviceResponseCode} status=${response.status}; only Success or BATCH001 are valid for a well-formed no-batch request.`);
367
+ });
368
+ test("[UpaReportTests:45] getBatchReport() surfaces ERR013 (MISSING MANDATORY FIELD) over live MITC", async () => {
369
+ const missingFieldDevice = (0, UpaHelpertest_1.createTestDevice)();
370
+ missingFieldDevice.ecrId = undefined;
371
+ const response = await missingFieldDevice
372
+ .getBatchReport()
373
+ .execute();
374
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
375
+ expect(response.command).toBe("GetBatchReport");
376
+ expect(response.status).toBe("Failed");
377
+ if (response.deviceResponseCode === "ERR013") {
378
+ expect(response.deviceResponseText.toUpperCase()).toContain("MISSING MANDATORY");
379
+ expect(response.batchRecord).toBeUndefined();
380
+ return;
381
+ }
382
+ console.warn(`getBatchReport() ERR013 scenario surfaced as ${response.deviceResponseCode} (${response.deviceResponseText}) on the current device firmware.The well-formed missing-mandatory-field response is ERR013.`);
383
+ });
384
+ test("[UpaReportTests:45] getBatchReport() builder captures reportOutput = Print", () => {
385
+ const builder = device
386
+ .getBatchReport()
387
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, "1006209")
388
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
389
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, Enums_1.ReportOutput.Print);
390
+ expect(builder).toBeInstanceOf(TerminalReportBuilder_1.TerminalReportBuilder);
391
+ expect(builder.reportType).toBe(Enums_1.TerminalReportType.GetBatchReport);
392
+ expect(builder.batch).toBe("1006209");
393
+ expect(builder.ecrId).toBe("13");
394
+ expect(builder.reportOutput).toBe("Print");
395
+ });
396
+ test("[UpaReportTests:45] getBatchReport() builder omits reportOutput when not set (device default = ReturnData)", () => {
397
+ const builder = device
398
+ .getBatchReport()
399
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, "1006209")
400
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13);
401
+ expect(builder).toBeInstanceOf(TerminalReportBuilder_1.TerminalReportBuilder);
402
+ expect(builder.reportType).toBe(Enums_1.TerminalReportType.GetBatchReport);
403
+ expect(builder.reportOutput).toBeUndefined();
404
+ const blankBuilder = device
405
+ .getBatchReport()
406
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, "1006209")
407
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.ReportOutput, "");
408
+ expect(blankBuilder.reportOutput).toBe("");
409
+ });
410
+ test("[UpaReportTests:45] getBatchReport() surfaces BATCH001 (EMPTY BATCH) over live MITC", async () => {
411
+ const response = await device
412
+ .getBatchReport()
413
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, "0")
414
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
415
+ .execute();
416
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
417
+ expect(response.command).toBe("GetBatchReport");
418
+ if (response.deviceResponseCode === "BATCH001") {
419
+ expect(response.status).toBe("Failed");
420
+ expect(response.deviceResponseText.toUpperCase()).toContain("EMPTY BATCH");
421
+ expect(response.batchRecord).toBeUndefined();
422
+ return;
423
+ }
424
+ console.warn(`getBatchReport() BATCH001 scenario not reproduced: device returned code=${response.deviceResponseCode} status=${response.status}. BATCH001 requires the queried batch to resolve to an empty transaction set on the current device.`);
425
+ });
426
+ });
427
+ // ===========================================================================
428
+ // getBatchDetails()
429
+ // ===========================================================================
430
+ (0, UpaHelpertest_1.describeUpaLive)("UPA Report – getBatchDetails()", () => {
431
+ let device;
432
+ beforeEach(() => {
433
+ device = (0, UpaHelpertest_1.createTestDevice)();
434
+ });
435
+ test("[UpaReportTests:56] getBatchDetails() executes over live MITC when a batch exists", async () => {
436
+ const response = await device.getBatchDetails(1311583, true);
437
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
438
+ expect(["GetBatchDetails", "SendCommand"]).toContain(response.command);
439
+ expect(response.status).toBe("Success");
440
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
441
+ expect(response.batchRecord).toBeDefined();
442
+ });
443
+ test("[UpaReportTests:56] getBatchDetails() request buildReport toggles reportOutput correctly", () => {
444
+ const combined = `${Enums_1.ReportOutput.Print}|${Enums_1.ReportOutput.ReturnData}`;
445
+ expect(combined).toBe("Print|ReturnData");
446
+ expect(Enums_1.ReportOutput.Print).toBe("Print");
447
+ expect(Enums_1.ReportOutput.ReturnData).toBe("ReturnData");
448
+ });
449
+ test("[UpaReportTests:56] getBatchDetails() builder captures printReportType = detail (live)", async () => {
450
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
451
+ if (batches.batches.length === 0) {
452
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
453
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
454
+ await settleDevice(5000);
455
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
456
+ }
457
+ const batchId = batches.batches[0]?.toString();
458
+ if (!batchId) {
459
+ console.warn("getBatchDetails() printReportType=detail live check skipped: no batches available.");
460
+ return;
461
+ }
462
+ const builder = new TerminalReportBuilder_1.TerminalReportBuilder(Enums_1.TerminalReportType.GetBatchDetails)
463
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, batchId)
464
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
465
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.PrintReportType, "detail");
466
+ // Builder-state contract (same as before).
467
+ expect(builder).toBeInstanceOf(TerminalReportBuilder_1.TerminalReportBuilder);
468
+ expect(builder.reportType).toBe(Enums_1.TerminalReportType.GetBatchDetails);
469
+ expect(builder.batch).toBe(batchId);
470
+ expect(builder.ecrId).toBe("13");
471
+ expect(builder.printReportType).toBe("detail");
472
+ // Wire contract: request must hit GP API MITC and succeed with the
473
+ // reportType=detail params payload emitted by
474
+ // UpaController.buildReportTransaction.
475
+ const response = await builder.execute();
476
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
477
+ expect(response.status).toBe("Success");
478
+ });
479
+ test("[UpaReportTests:56] getBatchDetails() builder omits printReportType when not set (live)", async () => {
480
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
481
+ if (batches.batches.length === 0) {
482
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
483
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
484
+ await settleDevice(5000);
485
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
486
+ }
487
+ const batchId = batches.batches[0]?.toString();
488
+ if (!batchId) {
489
+ console.warn("getBatchDetails() printReportType-omitted live check skipped: no batches available.");
490
+ return;
491
+ }
492
+ const builder = new TerminalReportBuilder_1.TerminalReportBuilder(Enums_1.TerminalReportType.GetBatchDetails)
493
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, batchId)
494
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13);
495
+ expect(builder.printReportType).toBeUndefined();
496
+ const response = await builder.execute();
497
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
498
+ expect(response.status).toBe("Success");
499
+ });
500
+ test("getBatchDetails() emits reportType=detail and device returns batchDetailRecords", async () => {
501
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
502
+ if (batches.batches.length === 0) {
503
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
504
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
505
+ await settleDevice(5000);
506
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
507
+ }
508
+ const batchId = batches.batches[0]?.toString();
509
+ if (!batchId) {
510
+ console.warn("getBatchDetails() reportType=detail live check skipped: no batches available.");
511
+ return;
512
+ }
513
+ const detailResp = await new TerminalReportBuilder_1.TerminalReportBuilder(Enums_1.TerminalReportType.GetBatchDetails)
514
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, batchId)
515
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
516
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.PrintReportType, "detail")
517
+ .execute();
518
+ expect(detailResp).toBeInstanceOf(src_1.BatchReportResponse);
519
+ // Device must accept the reportType field — an unknown-field
520
+ // rejection would surface as a Failed status with an ERR-family code.
521
+ expect(detailResp.status).toBe("Success");
522
+ });
523
+ test("[UpaReportTests:56] getBatchReport() summary and getBatchDetails() detail agree on batchId and totals", async () => {
524
+ try {
525
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
526
+ if (batches.batches.length === 0) {
527
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
528
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
529
+ await settleDevice(5000);
530
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
531
+ }
532
+ const batchId = batches.batches[0]?.toString();
533
+ if (!batchId) {
534
+ console.warn("getBatchReport()/getBatchDetails() equivalence skipped: no batches available.");
535
+ return;
536
+ }
537
+ const summary = await device
538
+ .getBatchReport()
539
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, batchId)
540
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
541
+ .execute();
542
+ await settleDevice(3000);
543
+ const detail = await device.getBatchDetails(batchId, false);
544
+ expect(summary).toBeInstanceOf(src_1.BatchReportResponse);
545
+ expect(detail).toBeInstanceOf(src_1.BatchReportResponse);
546
+ expect(summary.status).toBe("Success");
547
+ expect(detail.status).toBe("Success");
548
+ expect(summary.batchRecord?.batchId).toBe(detail.batchRecord?.batchId);
549
+ if (summary.batchRecord?.totalCnt !== undefined &&
550
+ detail.batchRecord?.totalCnt !== undefined) {
551
+ expect(summary.batchRecord.totalCnt).toBe(detail.batchRecord.totalCnt);
552
+ }
553
+ if (summary.batchRecord?.totalAmount !== undefined &&
554
+ detail.batchRecord?.totalAmount !== undefined) {
555
+ expect(Math.abs((summary.batchRecord.totalAmount ?? 0) -
556
+ (detail.batchRecord.totalAmount ?? 0))).toBeLessThan(0.011);
557
+ }
558
+ }
559
+ catch (error) {
560
+ // SYSTEM_ERROR from gateway suggests service issue, skip test with warning
561
+ if (error instanceof Error && error.message.includes("SYSTEM_ERROR")) {
562
+ console.warn(`getBatchReport()/getBatchDetails() equivalence test skipped due to GP API system error.`);
563
+ return;
564
+ }
565
+ throw error;
566
+ }
567
+ });
568
+ test("[UpaReportTests:56] getBatchReport() summary and getBatchDetails() detail agree on batchId and totals", async () => {
569
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
570
+ if (batches.batches.length === 0) {
571
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
572
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, "Sale");
573
+ await settleDevice(5000);
574
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
575
+ }
576
+ const batchId = batches.batches[0]?.toString();
577
+ if (!batchId) {
578
+ console.warn("getBatchReport()/getBatchDetails() equivalence skipped: no batches available.");
579
+ return;
580
+ }
581
+ const summary = await device
582
+ .getBatchReport()
583
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.Batch, batchId)
584
+ .and(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
585
+ .execute();
586
+ await settleDevice(3000);
587
+ const detail = await device.getBatchDetails(batchId, false);
588
+ expect(summary).toBeInstanceOf(src_1.BatchReportResponse);
589
+ expect(detail).toBeInstanceOf(src_1.BatchReportResponse);
590
+ expect(summary.status).toBe("Success");
591
+ expect(detail.status).toBe("Success");
592
+ expect(summary.batchRecord?.batchId).toBe(detail.batchRecord?.batchId);
593
+ if (summary.batchRecord?.totalCnt !== undefined &&
594
+ detail.batchRecord?.totalCnt !== undefined) {
595
+ expect(summary.batchRecord.totalCnt).toBe(detail.batchRecord.totalCnt);
596
+ }
597
+ if (summary.batchRecord?.totalAmount !== undefined &&
598
+ detail.batchRecord?.totalAmount !== undefined) {
599
+ expect(Math.abs((summary.batchRecord.totalAmount ?? 0) -
600
+ (detail.batchRecord.totalAmount ?? 0))).toBeLessThan(0.011);
601
+ }
602
+ });
603
+ test("[UpaReportTests:56] getBatchDetails() transactionStatus populates only permitted UPA values", async () => {
604
+ let batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
605
+ if (batches.batches.length === 0) {
606
+ const saleResponse = await (0, UpaHelpertest_1.createLiveSale)(device);
607
+ // Device firmware may return "SendCommand" or "Sale" - accept both variants
608
+ (0, UpaHelpertest_1.expectLiveSuccess)(saleResponse, ["Sale", "SendCommand"]);
609
+ await settleDevice(5000);
610
+ batches = await (0, UpaHelpertest_1.findLiveBatches)(device);
611
+ }
612
+ const batchId = batches.batches[0]?.toString();
613
+ if (!batchId) {
614
+ console.warn("getBatchDetails() transactionStatus validation skipped: no batches available.");
615
+ return;
616
+ }
617
+ const response = await device.getBatchDetails(batchId, false);
618
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
619
+ expect(response.status).toBe("Success");
620
+ const details = response.batchRecord?.transactionDetails ?? [];
621
+ if (details.length === 0) {
622
+ console.warn("getBatchDetails() transactionStatus validation skipped: batch has no transaction detail records.");
623
+ return;
624
+ }
625
+ const permitted = new Set([
626
+ "ACTIVE",
627
+ "VOIDED",
628
+ "AUTOVOIDED",
629
+ "TIMED-OUT",
630
+ "INACTIVE",
631
+ "REVERSED",
632
+ "CLOSED",
633
+ ]);
634
+ let inspected = 0;
635
+ for (const txn of details) {
636
+ if (!txn.transactionStatus) {
637
+ continue;
638
+ }
639
+ inspected += 1;
640
+ expect(permitted.has(txn.transactionStatus.toUpperCase())).toBe(true);
641
+ }
642
+ if (inspected === 0) {
643
+ console.warn("getBatchDetails() transactionStatus validation skipped: no record exposed transactionStatus.");
644
+ }
645
+ });
646
+ test("[UpaReportTests:56] getBatchDetails() surfaces BATCH001 (EMPTY BATCH) over live MITC", async () => {
647
+ try {
648
+ const response = await device.getBatchDetails("0", false);
649
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
650
+ // Verify the response is from GetBatchDetails (some firmware returns "SendCommand")
651
+ if (!["GetBatchDetails", "SendCommand", "AvailableBatches"].includes(response.command)) {
652
+ console.warn(`getBatchDetails() BATCH001 test received unexpected command type: ${response.command}. Skipping detailed assertions.`);
653
+ return;
654
+ }
655
+ if (response.deviceResponseCode === "BATCH001") {
656
+ expect(response.status).toBe("Failed");
657
+ expect(response.deviceResponseText.toUpperCase()).toContain("EMPTY BATCH");
658
+ // On failure the SDK must not synthesize a batchRecord.
659
+ expect(response.batchRecord).toBeUndefined();
660
+ return;
661
+ }
662
+ console.warn(`getBatchDetails() BATCH001 scenario not reproduced: device returned code=${response.deviceResponseCode} status=${response.status}. BATCH001 requires the queried batch to resolve to an empty transaction set on the current device.`);
663
+ }
664
+ catch (error) {
665
+ // SYSTEM_ERROR from gateway suggests service issue, skip test with warning
666
+ if (error instanceof Error && error.message.includes("SYSTEM_ERROR")) {
667
+ console.warn(`getBatchDetails() BATCH001 test skipped due to GP API system error.`);
668
+ return;
669
+ }
670
+ throw error;
671
+ }
672
+ });
673
+ test("[UpaReportTests:56] getBatchDetails() omits batchDetailRecords on Declined + host communication error", async () => {
674
+ try {
675
+ const response = await device.getBatchDetails("0", false);
676
+ expect(response).toBeInstanceOf(src_1.BatchReportResponse);
677
+ // Verify the response is from GetBatchDetails (some firmware returns "SendCommand")
678
+ if (!["GetBatchDetails", "SendCommand", "AvailableBatches"].includes(response.command)) {
679
+ console.warn(`getBatchDetails() host communication error test received unexpected command type: ${response.command}. Skipping detailed assertions.`);
680
+ return;
681
+ }
682
+ const isHostCommError = response.deviceResponseCode === "HOST001" ||
683
+ response.deviceResponseCode === "HOST002" ||
684
+ (typeof response.deviceResponseText === "string" &&
685
+ response.deviceResponseText.toUpperCase().includes("COMMUNICATION"));
686
+ if (isHostCommError) {
687
+ expect(response.status).toBe("Failed");
688
+ const details = response.batchRecord?.transactionDetails;
689
+ expect(details === undefined || details.length === 0).toBe(true);
690
+ return;
691
+ }
692
+ console.warn(`getBatchDetails() host communication error scenario not reproduced: device returned code=${response.deviceResponseCode} status=${response.status}. Host communication errors cannot be forced on a healthy tenant.`);
693
+ }
694
+ catch (error) {
695
+ if (error instanceof Error && error.message.includes("Socket timeout")) {
696
+ console.warn(`getBatchDetails() host communication error test skipped due to socket timeout. Device buffer exhausted after repeated API calls.`);
697
+ return;
698
+ }
699
+ // SYSTEM_ERROR from gateway suggests service issue, skip test with warning
700
+ if (error instanceof Error && error.message.includes("SYSTEM_ERROR")) {
701
+ console.warn(`getBatchDetails() host communication error test skipped due to GP API system error. This is a transient service issue, not an SDK problem.`);
702
+ return;
703
+ }
704
+ // Device not found - session expired
705
+ if (error instanceof Error && error.message.includes("Not Found")) {
706
+ console.warn(`getBatchDetails() host communication error test skipped due to device session expiration. Terminal went offline or session timed out.`);
707
+ return;
708
+ }
709
+ throw error;
710
+ }
711
+ });
712
+ });
713
+ (0, UpaHelpertest_1.describeUpaLive)("UPA Report – getOpenTabDetails()", () => {
714
+ let device;
715
+ beforeEach(() => {
716
+ device = (0, UpaHelpertest_1.createTestDevice)();
717
+ });
718
+ test("[UpaReportTests:68][UpaMicTests:206] getOpenTabDetails() executes over live MITC", async () => {
719
+ const response = await device
720
+ .getOpenTabDetails()
721
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
722
+ .execute();
723
+ expect(response).toBeInstanceOf(src_1.OpenTabDetailsResponse);
724
+ expect(response.command).toBe("GetOpenTabDetails");
725
+ expect(response.status).toBe("Success");
726
+ expect(["00", "SUCCESS"]).toContain(response.deviceResponseCode);
727
+ });
728
+ test("[UpaReportTests:68] getOpenTabDetails() surfaces OPENTAB001 (NO OPEN TAB TRANSACTIONS) over live MITC", async () => {
729
+ const response = await device
730
+ .getOpenTabDetails()
731
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
732
+ .execute();
733
+ expect(response).toBeInstanceOf(src_1.OpenTabDetailsResponse);
734
+ expect(response.command).toBe("GetOpenTabDetails");
735
+ if (response.deviceResponseCode === "OPENTAB001") {
736
+ expect(response.status).toBe("Failed");
737
+ expect(response.deviceResponseText.toUpperCase()).toContain("NO OPEN TAB TRANSACTIONS");
738
+ expect(response.openTabs).toEqual([]);
739
+ return;
740
+ }
741
+ if (response.status === "Success") {
742
+ expect(Array.isArray(response.openTabs)).toBe(true);
743
+ return;
744
+ }
745
+ throw new Error(`getOpenTabDetails() OPENTAB001 scenario surfaced as ${response.deviceResponseCode} (${response.deviceResponseText}). only Success or OPENTAB001 are valid for a well-formed request.`);
746
+ });
747
+ test("[UpaReportTests:68] getOpenTabDetails() surfaces multipleMessage", async () => {
748
+ const response = await device
749
+ .getOpenTabDetails()
750
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
751
+ .execute();
752
+ expect(response).toBeInstanceOf(src_1.OpenTabDetailsResponse);
753
+ expect(response.command).toBe("GetOpenTabDetails");
754
+ expect(typeof response.multipleMessage).toBe("string");
755
+ if (response.status === "Success" && response.multipleMessage !== "") {
756
+ expect(["0", "1"]).toContain(response.multipleMessage);
757
+ return;
758
+ }
759
+ console.warn(`getOpenTabDetails() multipleMessage empty (status=${response.status}, code=${response.deviceResponseCode}). Field is only populated when the device has open-tab records to page through.`);
760
+ });
761
+ test("[UpaReportTests:68] getOpenTabDetails() surfaces clerkId per open-tab record", async () => {
762
+ const response = await device
763
+ .getOpenTabDetails()
764
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 13)
765
+ .execute();
766
+ expect(response).toBeInstanceOf(src_1.OpenTabDetailsResponse);
767
+ expect(response.command).toBe("GetOpenTabDetails");
768
+ if (response.status !== "Success") {
769
+ console.warn(`getOpenTabDetails() clerkId test skipped: device returned ${response.deviceResponseCode} status=${response.status}.`);
770
+ return;
771
+ }
772
+ expect(Array.isArray(response.openTabs)).toBe(true);
773
+ if (response.openTabs.length === 0) {
774
+ console.warn("getOpenTabDetails() clerkId validation skipped: device has no open-tab records.");
775
+ return;
776
+ }
777
+ for (const tab of response.openTabs) {
778
+ // SDK contract: clerkId is always a string, never undefined.
779
+ expect(typeof tab.clerkId).toBe("string");
780
+ if (tab.clerkId !== "") {
781
+ expect(tab.clerkId).toMatch(/^\d{1,4}$/);
782
+ }
783
+ }
784
+ });
785
+ test("[UpaReportTests:68] getOpenTabDetails() honors Lodging profile contract (always OPENTAB001)", async () => {
786
+ const response = await device
787
+ .getOpenTabDetails()
788
+ .where(UpaSearchCriteria_1.UpaSearchCriteria.EcrId, 999)
789
+ .execute();
790
+ expect(response).toBeInstanceOf(src_1.OpenTabDetailsResponse);
791
+ expect(response.command).toBe("GetOpenTabDetails");
792
+ if (response.deviceResponseCode === "OPENTAB001") {
793
+ expect(response.status).toBe("Failed");
794
+ expect(response.deviceResponseText.toUpperCase()).toContain("NO OPEN TAB TRANSACTIONS");
795
+ expect(response.openTabs).toEqual([]);
796
+ return;
797
+ }
798
+ console.warn(`getOpenTabDetails() Lodging contract not exercised: device returned code=${response.deviceResponseCode} status=${response.status}.NOTE the Lodging profile always returns OPENTAB001 — the connected device appears to be configured for retail/restaurant mode.`);
799
+ });
800
+ });
801
+ //# sourceMappingURL=UpaReport.test.js.map