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.
- package/CHANGELOG.md +10 -1
- package/appsec.properties +1 -1
- package/lib/src/Builders/BaseBuilder/ValidationClause.d.ts +3 -2
- package/lib/src/Builders/BaseBuilder/ValidationClause.js +23 -15
- package/lib/src/Builders/BaseBuilder/ValidationClause.js.map +1 -1
- package/lib/src/Builders/BaseBuilder/Validations.js +7 -2
- package/lib/src/Builders/BaseBuilder/Validations.js.map +1 -1
- package/lib/src/Builders/RequestBuilder/GpEcom/GpEcomManagementRequestBuilder.js +2 -2
- package/lib/src/Builders/RequestBuilder/GpEcom/GpEcomManagementRequestBuilder.js.map +1 -1
- package/lib/src/Entities/Enums.d.ts +4 -2
- package/lib/src/Entities/Enums.js +3 -1
- package/lib/src/Entities/Enums.js.map +1 -1
- package/lib/src/Entities/RecurringEntity.js +2 -2
- package/lib/src/Entities/RecurringEntity.js.map +1 -1
- package/lib/src/Entities/UPA/ProcessIndicator.d.ts +1 -1
- package/lib/src/Entities/UPA/ProcessIndicator.js.map +1 -1
- package/lib/src/Entities/UPA/UpaParam.d.ts +7 -7
- package/lib/src/Entities/UPA/UpaParam.js.map +1 -1
- package/lib/src/Entities/UPA/UpaTransactionData.d.ts +5 -5
- package/lib/src/Entities/UPA/UpaTransactionData.js.map +1 -1
- package/lib/src/Entities/UPA/index.d.ts +3 -0
- package/lib/src/Entities/UPA/index.js +3 -0
- package/lib/src/Entities/UPA/index.js.map +1 -1
- package/lib/src/Gateways/GpApiConnector.js +2 -2
- package/lib/src/Gateways/GpApiConnector.js.map +1 -1
- package/lib/src/Gateways/PorticoConnector.d.ts +1 -1
- package/lib/src/Terminals/Abstractions/IDeviceInterface.d.ts +23 -2
- package/lib/src/Terminals/Abstractions/IDeviceResponse.d.ts +71 -24
- package/lib/src/Terminals/Builders/TerminalAuthBuilder.d.ts +54 -22
- package/lib/src/Terminals/Builders/TerminalAuthBuilder.js +98 -13
- package/lib/src/Terminals/Builders/TerminalAuthBuilder.js.map +1 -1
- package/lib/src/Terminals/Builders/TerminalBuilder.d.ts +6 -3
- package/lib/src/Terminals/Builders/TerminalBuilder.js +9 -0
- package/lib/src/Terminals/Builders/TerminalBuilder.js.map +1 -1
- package/lib/src/Terminals/Builders/TerminalManageBuilder.d.ts +16 -6
- package/lib/src/Terminals/Builders/TerminalManageBuilder.js +27 -2
- package/lib/src/Terminals/Builders/TerminalManageBuilder.js.map +1 -1
- package/lib/src/Terminals/Builders/TerminalReportBuilder.d.ts +2 -0
- package/lib/src/Terminals/Builders/TerminalReportBuilder.js +8 -0
- package/lib/src/Terminals/Builders/TerminalReportBuilder.js.map +1 -1
- package/lib/src/Terminals/Builders/index.d.ts +1 -0
- package/lib/src/Terminals/Builders/index.js +1 -0
- package/lib/src/Terminals/Builders/index.js.map +1 -1
- package/lib/src/Terminals/DeviceController.d.ts +6 -5
- package/lib/src/Terminals/DeviceController.js.map +1 -1
- package/lib/src/Terminals/DeviceInterface.d.ts +23 -3
- package/lib/src/Terminals/DeviceInterface.js +68 -0
- package/lib/src/Terminals/DeviceInterface.js.map +1 -1
- package/lib/src/Terminals/Enums.d.ts +11 -0
- package/lib/src/Terminals/Enums.js +14 -1
- package/lib/src/Terminals/Enums.js.map +1 -1
- package/lib/src/Terminals/TerminalResponse.d.ts +44 -36
- package/lib/src/Terminals/TerminalResponse.js +8 -0
- package/lib/src/Terminals/TerminalResponse.js.map +1 -1
- package/lib/src/Terminals/UPA/Entities/UpaMessageId.d.ts +10 -1
- package/lib/src/Terminals/UPA/Entities/UpaMessageId.js +9 -0
- package/lib/src/Terminals/UPA/Entities/UpaMessageId.js.map +1 -1
- package/lib/src/Terminals/UPA/Entities/UpaSearchCriteria.d.ts +3 -1
- package/lib/src/Terminals/UPA/Entities/UpaSearchCriteria.js +2 -0
- package/lib/src/Terminals/UPA/Entities/UpaSearchCriteria.js.map +1 -1
- package/lib/src/Terminals/UPA/Entities/index.d.ts +1 -0
- package/lib/src/Terminals/UPA/Entities/index.js +1 -0
- package/lib/src/Terminals/UPA/Entities/index.js.map +1 -1
- package/lib/src/Terminals/UPA/Interfaces/UpaMicInterface.d.ts +1 -1
- package/lib/src/Terminals/UPA/Interfaces/UpaMicInterface.js +7 -5
- package/lib/src/Terminals/UPA/Interfaces/UpaMicInterface.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/BatchList.d.ts +1 -0
- package/lib/src/Terminals/UPA/Reponses/BatchList.js +2 -0
- package/lib/src/Terminals/UPA/Reponses/BatchList.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/BatchReportResponse.d.ts +1 -0
- package/lib/src/Terminals/UPA/Reponses/BatchReportResponse.js +2 -0
- package/lib/src/Terminals/UPA/Reponses/BatchReportResponse.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/OpenTabDetailsResponse.d.ts +2 -0
- package/lib/src/Terminals/UPA/Reponses/OpenTabDetailsResponse.js +4 -0
- package/lib/src/Terminals/UPA/Reponses/OpenTabDetailsResponse.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/SafReportResponse.d.ts +3 -0
- package/lib/src/Terminals/UPA/Reponses/SafReportResponse.js +6 -0
- package/lib/src/Terminals/UPA/Reponses/SafReportResponse.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/TransactionResponse.d.ts +48 -25
- package/lib/src/Terminals/UPA/Reponses/TransactionResponse.js +151 -15
- package/lib/src/Terminals/UPA/Reponses/TransactionResponse.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/UpaSAFResponse.js +3 -2
- package/lib/src/Terminals/UPA/Reponses/UpaSAFResponse.js.map +1 -1
- package/lib/src/Terminals/UPA/Reponses/index.d.ts +8 -0
- package/lib/src/Terminals/UPA/Reponses/index.js +8 -0
- package/lib/src/Terminals/UPA/Reponses/index.js.map +1 -1
- package/lib/src/Terminals/UPA/UpaController.d.ts +17 -5
- package/lib/src/Terminals/UPA/UpaController.js +338 -79
- package/lib/src/Terminals/UPA/UpaController.js.map +1 -1
- package/lib/src/Terminals/UPA/UpaInterface.d.ts +26 -3
- package/lib/src/Terminals/UPA/UpaInterface.js +311 -12
- package/lib/src/Terminals/UPA/UpaInterface.js.map +1 -1
- package/lib/src/Terminals/index.d.ts +2 -0
- package/lib/src/Terminals/index.js +2 -0
- package/lib/src/Terminals/index.js.map +1 -1
- package/lib/src/Utils/StringUtils.js +1 -1
- package/lib/src/Utils/StringUtils.js.map +1 -1
- package/lib/test/Integration/Gateways/CI/upa_mitc/UpaFunctionalityTests.test.js +19 -8
- package/lib/test/Integration/Gateways/CI/upa_mitc/UpaFunctionalityTests.test.js.map +1 -1
- package/lib/test/Integration/Gateways/Terminals/RequestIdProvider.d.ts +15 -1
- package/lib/test/Integration/Gateways/Terminals/RequestIdProvider.js +88 -3
- package/lib/test/Integration/Gateways/Terminals/RequestIdProvider.js.map +1 -1
- package/lib/test/Unit/Terminals/UPA/UpaAdmin.test.d.ts +1 -0
- package/lib/test/Unit/Terminals/UPA/UpaAdmin.test.js +1260 -0
- package/lib/test/Unit/Terminals/UPA/UpaAdmin.test.js.map +1 -0
- package/lib/test/Unit/Terminals/UPA/UpaCredit.test.d.ts +1 -0
- package/lib/test/Unit/Terminals/UPA/UpaCredit.test.js +411 -0
- package/lib/test/Unit/Terminals/UPA/UpaCredit.test.js.map +1 -0
- package/lib/test/Unit/Terminals/UPA/UpaHelpertest.d.ts +55 -0
- package/lib/test/Unit/Terminals/UPA/UpaHelpertest.js +490 -0
- package/lib/test/Unit/Terminals/UPA/UpaHelpertest.js.map +1 -0
- package/lib/test/Unit/Terminals/UPA/UpaReport.test.d.ts +1 -0
- package/lib/test/Unit/Terminals/UPA/UpaReport.test.js +801 -0
- package/lib/test/Unit/Terminals/UPA/UpaReport.test.js.map +1 -0
- 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
|