@yoryoboy/bi-mcp 1.14.3 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +2 -2
- package/dist/mcp-use.json +2 -2
- package/dist/src/services/mercadolibre/mercadolibre-billing.js +99 -1
- package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-billing-provisions-summary.js +391 -0
- package/dist/src/tools/mercadolibre/get-billing-provisions-summary.js.map +7 -0
- package/dist/src/tools/mercadolibre/get-order-details.js +18 -4
- package/dist/src/tools/mercadolibre/get-order-details.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-orders-summary.js +6 -2
- package/dist/src/tools/mercadolibre/get-orders-summary.js.map +2 -2
- package/dist/src/tools/mercadolibre/get-shipping-summary.js +50 -14
- package/dist/src/tools/mercadolibre/get-shipping-summary.js.map +2 -2
- package/dist/src/tools/mercadolibre/index.js +1 -0
- package/dist/src/tools/mercadolibre/index.js.map +2 -2
- package/dist/tests/meli/mercadolibre-billing-provisions.test.js +940 -0
- package/dist/tests/meli/mercadolibre-billing-provisions.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js +16 -4
- package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js.map +2 -2
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js +25 -3
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js.map +2 -2
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js +141 -5
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js.map +2 -2
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js +3 -2
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js.map +2 -2
- package/dist/vitest.config.js +3 -1
- package/dist/vitest.config.js.map +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,940 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const { axiosGetMock, errorMock, objectMock, resolveProfileMock, formatErrorMock, apiContextMock } = vi.hoisted(() => ({
|
|
3
|
+
axiosGetMock: vi.fn(),
|
|
4
|
+
errorMock: vi.fn((message) => ({ kind: "error", message })),
|
|
5
|
+
objectMock: vi.fn((payload) => ({ kind: "object", payload })),
|
|
6
|
+
resolveProfileMock: vi.fn(),
|
|
7
|
+
formatErrorMock: vi.fn(
|
|
8
|
+
(err, fallback) => `formatted:${fallback}:${err instanceof Error ? err.message : String(err)}`
|
|
9
|
+
),
|
|
10
|
+
apiContextMock: vi.fn(
|
|
11
|
+
(profileId) => Promise.resolve({
|
|
12
|
+
sellerId: `seller-${profileId}`,
|
|
13
|
+
scopes: ["read"],
|
|
14
|
+
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
15
|
+
api: { get: axiosGetMock }
|
|
16
|
+
})
|
|
17
|
+
)
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("mcp-use/server", () => ({
|
|
20
|
+
object: objectMock,
|
|
21
|
+
error: errorMock
|
|
22
|
+
}));
|
|
23
|
+
vi.mock("../../src/services/mercadolibre/mercadolibre-api.js", () => ({
|
|
24
|
+
getMercadoLibreApiContext: apiContextMock,
|
|
25
|
+
formatMercadoLibreError: formatErrorMock
|
|
26
|
+
}));
|
|
27
|
+
vi.mock("../../src/tools/mercadolibre/profile-resolution.js", () => ({
|
|
28
|
+
resolveMercadoLibreProfileOrSelection: resolveProfileMock
|
|
29
|
+
}));
|
|
30
|
+
import {
|
|
31
|
+
getMercadoLibreBillingProvisionPage,
|
|
32
|
+
getMercadoLibreBillingProvisions,
|
|
33
|
+
scopeToGroupPath
|
|
34
|
+
} from "../../src/services/mercadolibre/mercadolibre-billing.js";
|
|
35
|
+
import {
|
|
36
|
+
meliGetBillingProvisionsSummaryHandler,
|
|
37
|
+
meliGetBillingProvisionsSummarySchema,
|
|
38
|
+
detailsSchema,
|
|
39
|
+
detailsSalesSchema,
|
|
40
|
+
detailsItemsSchema
|
|
41
|
+
} from "../../src/tools/mercadolibre/get-billing-provisions-summary.js";
|
|
42
|
+
function nestedRow(overrides = {}) {
|
|
43
|
+
const chargeInfo = overrides.charge_info ?? {};
|
|
44
|
+
const currencyInfo = overrides.currency_info ?? {};
|
|
45
|
+
const salesInfo = overrides.sales_info;
|
|
46
|
+
const itemsInfo = overrides.items_info;
|
|
47
|
+
const marketplaceInfo = overrides.marketplace_info;
|
|
48
|
+
return {
|
|
49
|
+
charge_info: {
|
|
50
|
+
detail_id: chargeInfo.detail_id ?? `id-${Math.random().toString(36).slice(2, 8)}`,
|
|
51
|
+
detail_amount: chargeInfo.detail_amount ?? 0,
|
|
52
|
+
detail_sub_type: chargeInfo.detail_sub_type ?? "",
|
|
53
|
+
concept_type: chargeInfo.concept_type ?? "",
|
|
54
|
+
transaction_detail: chargeInfo.transaction_detail ?? "",
|
|
55
|
+
detail_type: chargeInfo.detail_type ?? "",
|
|
56
|
+
creation_date_time: chargeInfo.creation_date_time ?? "",
|
|
57
|
+
...chargeInfo
|
|
58
|
+
},
|
|
59
|
+
currency_info: {
|
|
60
|
+
currency_id: currencyInfo.currency_id ?? "ARS",
|
|
61
|
+
...currencyInfo
|
|
62
|
+
},
|
|
63
|
+
sales_info: salesInfo ?? [],
|
|
64
|
+
items_info: itemsInfo ?? [],
|
|
65
|
+
marketplace_info: marketplaceInfo ?? {},
|
|
66
|
+
...overrides
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
describe("scopeToGroupPath", () => {
|
|
70
|
+
it("maps ml to ML", () => {
|
|
71
|
+
expect(scopeToGroupPath("ml")).toBe("ML");
|
|
72
|
+
});
|
|
73
|
+
it("maps mp to MP", () => {
|
|
74
|
+
expect(scopeToGroupPath("mp")).toBe("MP");
|
|
75
|
+
});
|
|
76
|
+
it("maps flex to ML/flex", () => {
|
|
77
|
+
expect(scopeToGroupPath("flex")).toBe("ML/flex");
|
|
78
|
+
});
|
|
79
|
+
it("maps full to ML/full", () => {
|
|
80
|
+
expect(scopeToGroupPath("full")).toBe("ML/full");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("getMercadoLibreBillingProvisionPage", () => {
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
vi.clearAllMocks();
|
|
86
|
+
});
|
|
87
|
+
function mockApiResponse(results = [], overrides = {}) {
|
|
88
|
+
const total = overrides.total ?? results.length;
|
|
89
|
+
const limit = overrides.limit ?? 1e3;
|
|
90
|
+
const last_id = overrides.last_id ?? (results.length > 0 ? "last-99" : null);
|
|
91
|
+
return {
|
|
92
|
+
data: { results, last_id, total, limit }
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
it("builds correct URL for flex scope", async () => {
|
|
96
|
+
axiosGetMock.mockResolvedValue(mockApiResponse());
|
|
97
|
+
await getMercadoLibreBillingProvisionPage("profile-1", {
|
|
98
|
+
periodKey: "2026-06-01",
|
|
99
|
+
scope: "flex",
|
|
100
|
+
documentType: "BILL",
|
|
101
|
+
limit: 1e3
|
|
102
|
+
});
|
|
103
|
+
const call = axiosGetMock.mock.calls[0];
|
|
104
|
+
expect(call[0]).toContain("/group/ML/flex/details");
|
|
105
|
+
expect(call[1].params.document_type).toBe("BILL");
|
|
106
|
+
expect(call[1].params.limit).toBe(1e3);
|
|
107
|
+
});
|
|
108
|
+
it("builds correct URL for mp scope", async () => {
|
|
109
|
+
axiosGetMock.mockResolvedValue(mockApiResponse());
|
|
110
|
+
await getMercadoLibreBillingProvisionPage("profile-1", {
|
|
111
|
+
periodKey: "2026-06-01",
|
|
112
|
+
scope: "mp",
|
|
113
|
+
documentType: "BILL",
|
|
114
|
+
limit: 1e3
|
|
115
|
+
});
|
|
116
|
+
expect(axiosGetMock.mock.calls[0][0]).toContain("/group/MP/details");
|
|
117
|
+
});
|
|
118
|
+
it("passes from_id when provided", async () => {
|
|
119
|
+
axiosGetMock.mockResolvedValue(mockApiResponse());
|
|
120
|
+
await getMercadoLibreBillingProvisionPage("profile-1", {
|
|
121
|
+
periodKey: "2026-06-01",
|
|
122
|
+
scope: "ml",
|
|
123
|
+
documentType: "BILL",
|
|
124
|
+
limit: 1e3,
|
|
125
|
+
fromId: "abc-123"
|
|
126
|
+
});
|
|
127
|
+
expect(axiosGetMock.mock.calls[0][1].params.from_id).toBe("abc-123");
|
|
128
|
+
});
|
|
129
|
+
it("passes optional filter params", async () => {
|
|
130
|
+
axiosGetMock.mockResolvedValue(mockApiResponse());
|
|
131
|
+
await getMercadoLibreBillingProvisionPage("profile-1", {
|
|
132
|
+
periodKey: "2026-06-01",
|
|
133
|
+
scope: "ml",
|
|
134
|
+
documentType: "BILL",
|
|
135
|
+
limit: 1e3,
|
|
136
|
+
detailType: "sale",
|
|
137
|
+
detailSubTypes: ["commission"],
|
|
138
|
+
marketplaceType: "classic",
|
|
139
|
+
orderIds: ["O1"],
|
|
140
|
+
itemIds: ["I1"]
|
|
141
|
+
});
|
|
142
|
+
const p = axiosGetMock.mock.calls[0][1].params;
|
|
143
|
+
expect(p.detail_type).toBe("sale");
|
|
144
|
+
expect(p.detail_sub_types).toBe("commission");
|
|
145
|
+
expect(p.marketplace_type).toBe("classic");
|
|
146
|
+
expect(p.order_ids).toBe("O1");
|
|
147
|
+
expect(p.item_ids).toBe("I1");
|
|
148
|
+
});
|
|
149
|
+
it("reads flat total/limit from real MELI envelope (no paging nesting)", async () => {
|
|
150
|
+
axiosGetMock.mockResolvedValue({
|
|
151
|
+
data: {
|
|
152
|
+
results: [nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })],
|
|
153
|
+
last_id: "last-1",
|
|
154
|
+
total: 42,
|
|
155
|
+
limit: 500
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
const page = await getMercadoLibreBillingProvisionPage("profile-1", {
|
|
159
|
+
periodKey: "2026-06-01",
|
|
160
|
+
scope: "ml",
|
|
161
|
+
documentType: "BILL",
|
|
162
|
+
limit: 1e3
|
|
163
|
+
});
|
|
164
|
+
expect(page.total).toBe(42);
|
|
165
|
+
expect(page.limit).toBe(500);
|
|
166
|
+
expect(page.paging).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("getMercadoLibreBillingProvisions", () => {
|
|
170
|
+
beforeEach(() => {
|
|
171
|
+
vi.clearAllMocks();
|
|
172
|
+
});
|
|
173
|
+
function mockApi(results, last_id, total, limit = 1e3) {
|
|
174
|
+
return { data: { results, last_id, total, limit } };
|
|
175
|
+
}
|
|
176
|
+
it("deduplicates by charge_info.detail_id across pages", async () => {
|
|
177
|
+
axiosGetMock.mockResolvedValueOnce(mockApi([
|
|
178
|
+
nestedRow({ charge_info: { detail_id: "a", detail_amount: 10 } }),
|
|
179
|
+
nestedRow({ charge_info: { detail_id: "b", detail_amount: 20 } })
|
|
180
|
+
], "last-1", 3)).mockResolvedValueOnce(mockApi([
|
|
181
|
+
nestedRow({ charge_info: { detail_id: "b", detail_amount: 20 } }),
|
|
182
|
+
// duplicate
|
|
183
|
+
nestedRow({ charge_info: { detail_id: "c", detail_amount: 30 } })
|
|
184
|
+
], null, 3));
|
|
185
|
+
const result = await getMercadoLibreBillingProvisions("profile-1", {
|
|
186
|
+
periodKey: "2026-06-01",
|
|
187
|
+
scope: "ml",
|
|
188
|
+
documentType: "BILL"
|
|
189
|
+
});
|
|
190
|
+
expect(result.detail_count_total).toBe(3);
|
|
191
|
+
expect(result.coverage_complete).toBe(true);
|
|
192
|
+
expect(result.rows.length).toBe(3);
|
|
193
|
+
});
|
|
194
|
+
it("stops when last_id does not advance", async () => {
|
|
195
|
+
axiosGetMock.mockResolvedValueOnce(mockApi([
|
|
196
|
+
nestedRow({ charge_info: { detail_id: "a", detail_amount: 10 } })
|
|
197
|
+
], "stuck-id", 1)).mockResolvedValueOnce(mockApi([
|
|
198
|
+
nestedRow({ charge_info: { detail_id: "b", detail_amount: 20 } })
|
|
199
|
+
], "stuck-id", 2));
|
|
200
|
+
const result = await getMercadoLibreBillingProvisions("profile-1", {
|
|
201
|
+
periodKey: "2026-06-01",
|
|
202
|
+
scope: "ml",
|
|
203
|
+
documentType: "BILL"
|
|
204
|
+
});
|
|
205
|
+
expect(result.pages_requested).toBe(2);
|
|
206
|
+
expect(result.detail_count_total).toBe(2);
|
|
207
|
+
expect(result.coverage_complete).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
it("returns coverage_complete=false with failures and breaks on error", async () => {
|
|
210
|
+
axiosGetMock.mockResolvedValueOnce(mockApi([
|
|
211
|
+
nestedRow({ charge_info: { detail_id: "a", detail_amount: 10 } })
|
|
212
|
+
], "page-1-last", 10)).mockRejectedValueOnce(new Error("Network error"));
|
|
213
|
+
const result = await getMercadoLibreBillingProvisions("profile-1", {
|
|
214
|
+
periodKey: "2026-06-01",
|
|
215
|
+
scope: "mp",
|
|
216
|
+
documentType: "BILL"
|
|
217
|
+
});
|
|
218
|
+
expect(result.pages_requested).toBe(2);
|
|
219
|
+
expect(result.pages_succeeded).toBe(1);
|
|
220
|
+
expect(result.pages_failed).toBe(1);
|
|
221
|
+
expect(result.coverage_complete).toBe(false);
|
|
222
|
+
expect(result.failures).toHaveLength(1);
|
|
223
|
+
expect(result.failures[0].page).toBe(2);
|
|
224
|
+
expect(result.rows.length).toBe(1);
|
|
225
|
+
});
|
|
226
|
+
it("handles empty results gracefully", async () => {
|
|
227
|
+
axiosGetMock.mockResolvedValueOnce(mockApi([], null, 0));
|
|
228
|
+
const result = await getMercadoLibreBillingProvisions("profile-1", {
|
|
229
|
+
periodKey: "2026-06-01",
|
|
230
|
+
scope: "full",
|
|
231
|
+
documentType: "BILL"
|
|
232
|
+
});
|
|
233
|
+
expect(result.pages_requested).toBe(1);
|
|
234
|
+
expect(result.detail_count_total).toBe(0);
|
|
235
|
+
expect(result.coverage_complete).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
describe("meliGetBillingProvisionsSummaryHandler", () => {
|
|
239
|
+
beforeEach(() => {
|
|
240
|
+
vi.clearAllMocks();
|
|
241
|
+
vi.mocked(resolveProfileMock).mockResolvedValue({
|
|
242
|
+
ok: true,
|
|
243
|
+
value: { profileId: "meli-profile-1" }
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
function mockApi(results, last_id = null, total) {
|
|
247
|
+
return { data: { results, last_id, total: total ?? results.length, limit: 1e3 } };
|
|
248
|
+
}
|
|
249
|
+
it("validates schema: periodKey must be YYYY-MM-01 format", () => {
|
|
250
|
+
const valid = "2026-06-01";
|
|
251
|
+
const invalid = "2026-06-15";
|
|
252
|
+
expect(() => meliGetBillingProvisionsSummarySchema.parse({ periodKey: valid, scope: "ml" })).not.toThrow();
|
|
253
|
+
expect(() => meliGetBillingProvisionsSummarySchema.parse({ periodKey: invalid, scope: "ml" })).toThrow();
|
|
254
|
+
});
|
|
255
|
+
it("validates schema: scope must be valid enum", () => {
|
|
256
|
+
expect(
|
|
257
|
+
() => meliGetBillingProvisionsSummarySchema.parse({ periodKey: "2026-06-01", scope: "ml" })
|
|
258
|
+
).not.toThrow();
|
|
259
|
+
expect(
|
|
260
|
+
() => meliGetBillingProvisionsSummarySchema.parse({ periodKey: "2026-06-01", scope: "invalid" })
|
|
261
|
+
).toThrow();
|
|
262
|
+
});
|
|
263
|
+
it("aggregates single scope with currency totals using nested payload shape", async () => {
|
|
264
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
265
|
+
nestedRow({
|
|
266
|
+
charge_info: { detail_id: "d1", detail_amount: 100, detail_sub_type: "CVFV", transaction_detail: "Cargo por vender" },
|
|
267
|
+
sales_info: [{ order_id: "O1" }],
|
|
268
|
+
items_info: [{ item_id: "I1" }]
|
|
269
|
+
}),
|
|
270
|
+
nestedRow({
|
|
271
|
+
charge_info: { detail_id: "d2", detail_amount: 25, detail_sub_type: "CFF", transaction_detail: "Cargo por env\xEDos de Mercado Libre" },
|
|
272
|
+
sales_info: [{ order_id: "O2" }]
|
|
273
|
+
}),
|
|
274
|
+
nestedRow({
|
|
275
|
+
charge_info: { detail_id: "d3", detail_amount: 150, detail_sub_type: "CVFV", transaction_detail: "Cargo por vender" },
|
|
276
|
+
currency_info: { currency_id: "USD" },
|
|
277
|
+
sales_info: [{ order_id: "O3" }]
|
|
278
|
+
})
|
|
279
|
+
]));
|
|
280
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
281
|
+
periodKey: "2026-06-01",
|
|
282
|
+
scope: "ml",
|
|
283
|
+
documentType: "BILL"
|
|
284
|
+
});
|
|
285
|
+
expect(result).toHaveProperty("kind", "object");
|
|
286
|
+
const payload = result.payload;
|
|
287
|
+
expect(payload.profile_id).toBe("meli-profile-1");
|
|
288
|
+
expect(payload.scope).toBe("ml");
|
|
289
|
+
expect(payload.details_schema).toEqual(detailsSchema);
|
|
290
|
+
const details = payload.details;
|
|
291
|
+
expect(details).toHaveLength(3);
|
|
292
|
+
for (const row of details) {
|
|
293
|
+
expect(Array.isArray(row)).toBe(true);
|
|
294
|
+
expect(row).toHaveLength(detailsSchema.length);
|
|
295
|
+
}
|
|
296
|
+
expect(details[0][0]).toBe("d1");
|
|
297
|
+
expect(details[0][4]).toBe(100);
|
|
298
|
+
expect(details[0][5]).toBe("ARS");
|
|
299
|
+
expect(details[0][8]).toBe("O1");
|
|
300
|
+
expect(details[0][10]).toBe("I1");
|
|
301
|
+
expect(details[2][5]).toBe("USD");
|
|
302
|
+
const summary = payload.summary;
|
|
303
|
+
expect(summary.detail_count).toBe(3);
|
|
304
|
+
expect(summary.order_count_detected).toBe(3);
|
|
305
|
+
expect(summary.item_count_detected).toBe(1);
|
|
306
|
+
const currTotals = summary.currency_totals;
|
|
307
|
+
expect(currTotals).toHaveProperty("ARS");
|
|
308
|
+
expect(currTotals).toHaveProperty("USD");
|
|
309
|
+
const arsBucket = currTotals.ARS;
|
|
310
|
+
expect(Object.keys(arsBucket).sort()).toEqual([
|
|
311
|
+
"bonus_amount_total",
|
|
312
|
+
"charge_amount_total",
|
|
313
|
+
"discount_amount_total",
|
|
314
|
+
"net_effect_total"
|
|
315
|
+
]);
|
|
316
|
+
expect(arsBucket.charge_amount_total).toBe(125);
|
|
317
|
+
expect(arsBucket.bonus_amount_total).toBe(0);
|
|
318
|
+
expect(arsBucket.discount_amount_total).toBe(0);
|
|
319
|
+
expect(arsBucket.net_effect_total).toBe(125);
|
|
320
|
+
expect(currTotals.USD.charge_amount_total).toBe(150);
|
|
321
|
+
expect(summary.totals_by_concept).toBeUndefined();
|
|
322
|
+
expect(summary.totals_by_concept_by_currency).toBeUndefined();
|
|
323
|
+
expect(payload.raw_sections).toBeUndefined();
|
|
324
|
+
expect(payload.enriched_sections).toBeUndefined();
|
|
325
|
+
expect(payload.classification_diagnostics).toBeUndefined();
|
|
326
|
+
expect(payload.summary_by_scope).toBeUndefined();
|
|
327
|
+
const diag = payload.diagnostics;
|
|
328
|
+
expect(diag).toHaveProperty("detail_sub_types_observed");
|
|
329
|
+
expect(diag).toHaveProperty("marketplaces_observed");
|
|
330
|
+
expect(diag).toHaveProperty("concept_types_observed");
|
|
331
|
+
expect(Array.isArray(diag.detail_sub_types_observed)).toBe(true);
|
|
332
|
+
const metadata = payload.metadata;
|
|
333
|
+
expect(metadata.coverage_complete).toBe(true);
|
|
334
|
+
expect(metadata.coverage_by_scope).toBeDefined();
|
|
335
|
+
});
|
|
336
|
+
it("handles scope=all by calling all four scopes with nested payload", async () => {
|
|
337
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
338
|
+
nestedRow({
|
|
339
|
+
charge_info: { detail_id: "shared-1", detail_amount: 50, detail_sub_type: "sale" },
|
|
340
|
+
sales_info: [{ order_id: "O-COMMON" }],
|
|
341
|
+
items_info: [{ item_id: "I-COMMON" }]
|
|
342
|
+
})
|
|
343
|
+
]));
|
|
344
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
345
|
+
periodKey: "2026-06-01",
|
|
346
|
+
scope: "all",
|
|
347
|
+
documentType: "BILL"
|
|
348
|
+
});
|
|
349
|
+
const payload = result.payload;
|
|
350
|
+
expect(payload.scope).toBe("all");
|
|
351
|
+
const summary = payload.summary;
|
|
352
|
+
expect(summary.detail_count).toBe(4);
|
|
353
|
+
expect(summary.order_count_detected).toBe(1);
|
|
354
|
+
expect(summary.item_count_detected).toBe(1);
|
|
355
|
+
const details = payload.details;
|
|
356
|
+
expect(details).toHaveLength(4);
|
|
357
|
+
const metadata = payload.metadata;
|
|
358
|
+
expect(metadata.sources_called).toBe(4);
|
|
359
|
+
expect(metadata.pages_succeeded).toBe(4);
|
|
360
|
+
expect(metadata.coverage_complete).toBe(true);
|
|
361
|
+
expect(metadata.coverage_by_scope).toBeDefined();
|
|
362
|
+
});
|
|
363
|
+
it("handles credit notes with negative amounts \u2014 nested shape", async () => {
|
|
364
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
365
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 500, detail_sub_type: "CVFV", transaction_detail: "Cargo por vender" } }),
|
|
366
|
+
nestedRow({ charge_info: { detail_id: "d2", detail_amount: -150, detail_sub_type: "BVFV", transaction_detail: "Anulaci\xF3n del cargo por vender" } }),
|
|
367
|
+
nestedRow({ charge_info: { detail_id: "d3", detail_amount: -50, detail_sub_type: "BFF", transaction_detail: "Anulaci\xF3n del cargo por env\xEDos" } })
|
|
368
|
+
]));
|
|
369
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
370
|
+
periodKey: "2026-06-01",
|
|
371
|
+
scope: "ml",
|
|
372
|
+
documentType: "CREDIT_NOTE"
|
|
373
|
+
});
|
|
374
|
+
const payload = result.payload;
|
|
375
|
+
expect(payload.document_type).toBe("CREDIT_NOTE");
|
|
376
|
+
const currTotals = payload.summary.currency_totals;
|
|
377
|
+
expect(currTotals.ARS.charge_amount_total).toBe(500);
|
|
378
|
+
expect(currTotals.ARS.bonus_amount_total).toBe(-200);
|
|
379
|
+
expect(currTotals.ARS.discount_amount_total).toBe(0);
|
|
380
|
+
expect(currTotals.ARS.net_effect_total).toBe(300);
|
|
381
|
+
});
|
|
382
|
+
it("handles mixed positive and negative amounts within same concept (nested)", async () => {
|
|
383
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
384
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 200, detail_sub_type: "CVFV", transaction_detail: "Cargo por vender" } }),
|
|
385
|
+
nestedRow({ charge_info: { detail_id: "d2", detail_amount: -80, detail_sub_type: "BVFF", transaction_detail: "Anulaci\xF3n del costo por unidad vendida" } }),
|
|
386
|
+
nestedRow({ charge_info: { detail_id: "d3", detail_amount: 50, detail_sub_type: "CFF", transaction_detail: "Cargo por env\xEDos de Mercado Libre" } }),
|
|
387
|
+
nestedRow({ charge_info: { detail_id: "d4", detail_amount: -20, detail_sub_type: "BXD", transaction_detail: "Anulaci\xF3n del cargo por env\xEDos" } })
|
|
388
|
+
]));
|
|
389
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
390
|
+
periodKey: "2026-06-01",
|
|
391
|
+
scope: "ml",
|
|
392
|
+
documentType: "BILL"
|
|
393
|
+
});
|
|
394
|
+
const payload = result.payload;
|
|
395
|
+
const currTotals = payload.summary.currency_totals;
|
|
396
|
+
expect(currTotals.ARS.charge_amount_total).toBe(250);
|
|
397
|
+
expect(currTotals.ARS.bonus_amount_total).toBe(-100);
|
|
398
|
+
expect(currTotals.ARS.discount_amount_total).toBe(0);
|
|
399
|
+
expect(currTotals.ARS.net_effect_total).toBe(150);
|
|
400
|
+
});
|
|
401
|
+
it("deduplicates order and item counts globally across scopes", async () => {
|
|
402
|
+
let callCount = 0;
|
|
403
|
+
axiosGetMock.mockImplementation(() => {
|
|
404
|
+
callCount++;
|
|
405
|
+
const scopeIndex = callCount - 1;
|
|
406
|
+
const scopes = ["ml", "mp", "flex", "full"];
|
|
407
|
+
const isPayment = scopes[scopeIndex] === "mp";
|
|
408
|
+
return Promise.resolve(mockApi([
|
|
409
|
+
nestedRow({
|
|
410
|
+
charge_info: {
|
|
411
|
+
detail_id: `d-${scopes[scopeIndex]}`,
|
|
412
|
+
detail_amount: 50,
|
|
413
|
+
detail_sub_type: isPayment ? "payment" : "sale"
|
|
414
|
+
},
|
|
415
|
+
sales_info: [{ order_id: "ORDER-1" }],
|
|
416
|
+
items_info: [{ item_id: scopeIndex < 2 ? "ITEM-A" : "ITEM-B" }]
|
|
417
|
+
})
|
|
418
|
+
]));
|
|
419
|
+
});
|
|
420
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
421
|
+
periodKey: "2026-06-01",
|
|
422
|
+
scope: "all",
|
|
423
|
+
documentType: "BILL"
|
|
424
|
+
});
|
|
425
|
+
const payload = result.payload;
|
|
426
|
+
const summary = payload.summary;
|
|
427
|
+
expect(summary.detail_count).toBe(4);
|
|
428
|
+
expect(summary.order_count_detected).toBe(1);
|
|
429
|
+
expect(summary.item_count_detected).toBe(2);
|
|
430
|
+
});
|
|
431
|
+
it("includes coverage_by_scope with per-scope operational metadata", async () => {
|
|
432
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
433
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })
|
|
434
|
+
]));
|
|
435
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
436
|
+
periodKey: "2026-06-01",
|
|
437
|
+
scope: "ml",
|
|
438
|
+
documentType: "BILL"
|
|
439
|
+
});
|
|
440
|
+
const payload = result.payload;
|
|
441
|
+
const covByScope = payload.metadata.coverage_by_scope;
|
|
442
|
+
expect(covByScope).toHaveProperty("ml");
|
|
443
|
+
expect(covByScope.ml.coverage_complete).toBe(true);
|
|
444
|
+
expect(covByScope.ml.last_id_seen).toBeUndefined();
|
|
445
|
+
expect(typeof covByScope.ml.pages_requested).toBe("number");
|
|
446
|
+
expect(typeof covByScope.ml.pages_succeeded).toBe("number");
|
|
447
|
+
expect(typeof covByScope.ml.pages_failed).toBe("number");
|
|
448
|
+
expect(typeof covByScope.ml.detail_count).toBe("number");
|
|
449
|
+
});
|
|
450
|
+
it("continuation_by_scope is absent when coverage is complete", async () => {
|
|
451
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
452
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })
|
|
453
|
+
]));
|
|
454
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
455
|
+
periodKey: "2026-06-01",
|
|
456
|
+
scope: "ml",
|
|
457
|
+
documentType: "BILL"
|
|
458
|
+
});
|
|
459
|
+
const payload = result.payload;
|
|
460
|
+
expect(payload.metadata.continuation_by_scope).toBeUndefined();
|
|
461
|
+
});
|
|
462
|
+
it("reports coverage_complete=false and continuation_by_scope when a scope has partial coverage", async () => {
|
|
463
|
+
axiosGetMock.mockResolvedValueOnce(mockApi([
|
|
464
|
+
nestedRow({ charge_info: { detail_id: "a", detail_amount: 10 } })
|
|
465
|
+
], "page-1-last", 10)).mockRejectedValueOnce(new Error("Network error"));
|
|
466
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
467
|
+
periodKey: "2026-06-01",
|
|
468
|
+
scope: "ml",
|
|
469
|
+
documentType: "BILL"
|
|
470
|
+
});
|
|
471
|
+
const payload = result.payload;
|
|
472
|
+
const metadata = payload.metadata;
|
|
473
|
+
expect(metadata.coverage_complete).toBe(false);
|
|
474
|
+
expect(metadata.pages_succeeded).toBe(1);
|
|
475
|
+
expect(metadata.pages_failed).toBe(1);
|
|
476
|
+
expect(metadata.coverage_by_scope).toBeDefined();
|
|
477
|
+
const covByScope = metadata.coverage_by_scope;
|
|
478
|
+
expect(covByScope.ml.coverage_complete).toBe(false);
|
|
479
|
+
expect(covByScope.ml.last_id_seen).toBe("page-1-last");
|
|
480
|
+
expect(metadata.continuation_by_scope).toBeDefined();
|
|
481
|
+
const continuation = metadata.continuation_by_scope;
|
|
482
|
+
expect(continuation.ml.last_id).toBe("page-1-last");
|
|
483
|
+
expect(typeof continuation.ml.hint).toBe("string");
|
|
484
|
+
expect(continuation.ml.hint).toContain("fromIdByScope");
|
|
485
|
+
expect(continuation.ml.hint).toContain("page-1-last");
|
|
486
|
+
});
|
|
487
|
+
it("multi-currency: totals_by_concept is absent \u2014 only currency_totals with four fields", async () => {
|
|
488
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
489
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 1e3, detail_sub_type: "CVFV", transaction_detail: "Cargo por vender" } }),
|
|
490
|
+
nestedRow({ charge_info: { detail_id: "d2", detail_amount: 200, detail_sub_type: "CFF", transaction_detail: "Cargo por env\xEDos de Mercado Libre" } }),
|
|
491
|
+
nestedRow({
|
|
492
|
+
charge_info: { detail_id: "d3", detail_amount: 50, detail_sub_type: "CVFV", transaction_detail: "Cargo por vender" },
|
|
493
|
+
currency_info: { currency_id: "USD" }
|
|
494
|
+
}),
|
|
495
|
+
nestedRow({
|
|
496
|
+
charge_info: { detail_id: "d4", detail_amount: 10, detail_sub_type: "CVFN", transaction_detail: "Costo por ofrecer cuotas" },
|
|
497
|
+
currency_info: { currency_id: "USD" }
|
|
498
|
+
}),
|
|
499
|
+
nestedRow({
|
|
500
|
+
charge_info: { detail_id: "d5", detail_amount: -5, detail_sub_type: "BVFV", transaction_detail: "Anulaci\xF3n del cargo por vender" },
|
|
501
|
+
currency_info: { currency_id: "USD" }
|
|
502
|
+
})
|
|
503
|
+
]));
|
|
504
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
505
|
+
periodKey: "2026-06-01",
|
|
506
|
+
scope: "ml",
|
|
507
|
+
documentType: "BILL"
|
|
508
|
+
});
|
|
509
|
+
const payload = result.payload;
|
|
510
|
+
const summary = payload.summary;
|
|
511
|
+
expect(summary.totals_by_concept).toBeUndefined();
|
|
512
|
+
expect(summary.totals_by_concept_by_currency).toBeUndefined();
|
|
513
|
+
const currTotals = summary.currency_totals;
|
|
514
|
+
expect(currTotals.ARS.charge_amount_total).toBe(1200);
|
|
515
|
+
expect(currTotals.ARS.bonus_amount_total).toBe(0);
|
|
516
|
+
expect(currTotals.ARS.net_effect_total).toBe(1200);
|
|
517
|
+
expect(currTotals.USD.charge_amount_total).toBe(60);
|
|
518
|
+
expect(currTotals.USD.bonus_amount_total).toBe(-5);
|
|
519
|
+
expect(currTotals.USD.net_effect_total).toBe(55);
|
|
520
|
+
expect(currTotals.ARS.by_concept).toBeUndefined();
|
|
521
|
+
expect(currTotals.USD.by_concept).toBeUndefined();
|
|
522
|
+
});
|
|
523
|
+
it("details rows are reversible with compact schema", async () => {
|
|
524
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
525
|
+
nestedRow({
|
|
526
|
+
charge_info: {
|
|
527
|
+
detail_id: "rev-1",
|
|
528
|
+
detail_amount: 500,
|
|
529
|
+
detail_sub_type: "CVFV",
|
|
530
|
+
transaction_detail: "Cargo por vender",
|
|
531
|
+
detail_type: "CHARGE",
|
|
532
|
+
concept_type: "sale",
|
|
533
|
+
creation_date_time: "2026-06-15T10:30:00Z"
|
|
534
|
+
},
|
|
535
|
+
currency_info: { currency_id: "ARS" },
|
|
536
|
+
marketplace_info: { marketplace: "MCLICS" },
|
|
537
|
+
sales_info: [{ order_id: "ORD-100", operation_id: "OP-200", shipping_id: "SH-300", pack_id: "PK-400" }],
|
|
538
|
+
items_info: [{ item_id: "MLB-500", item_title: "Zapatillas Nike" }],
|
|
539
|
+
document_id: "DOC-999"
|
|
540
|
+
})
|
|
541
|
+
]));
|
|
542
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
543
|
+
periodKey: "2026-06-01",
|
|
544
|
+
scope: "ml",
|
|
545
|
+
documentType: "BILL"
|
|
546
|
+
});
|
|
547
|
+
const payload = result.payload;
|
|
548
|
+
const schema = payload.details_schema;
|
|
549
|
+
const details = payload.details;
|
|
550
|
+
const row = details[0];
|
|
551
|
+
const reconstructed = {};
|
|
552
|
+
schema.forEach((key, i) => {
|
|
553
|
+
reconstructed[key] = row[i];
|
|
554
|
+
});
|
|
555
|
+
expect(reconstructed.detail_id).toBe("rev-1");
|
|
556
|
+
expect(reconstructed.detail_type).toBe("CHARGE");
|
|
557
|
+
expect(reconstructed.detail_sub_type).toBe("CVFV");
|
|
558
|
+
expect(reconstructed.transaction_detail).toBe("Cargo por vender");
|
|
559
|
+
expect(reconstructed.detail_amount).toBe(500);
|
|
560
|
+
expect(reconstructed.currency_id).toBe("ARS");
|
|
561
|
+
expect(reconstructed.marketplace).toBe("MCLICS");
|
|
562
|
+
expect(reconstructed.concept_type).toBe("sale");
|
|
563
|
+
expect(reconstructed.order_id).toBe("ORD-100");
|
|
564
|
+
expect(reconstructed.operation_id).toBe("OP-200");
|
|
565
|
+
expect(reconstructed.item_id).toBe("MLB-500");
|
|
566
|
+
expect(reconstructed.item_title).toBe("Zapatillas Nike");
|
|
567
|
+
expect(reconstructed.shipping_id).toBe("SH-300");
|
|
568
|
+
expect(reconstructed.pack_id).toBe("PK-400");
|
|
569
|
+
expect(reconstructed.document_id).toBe("DOC-999");
|
|
570
|
+
expect(reconstructed.creation_date_time).toBe("2026-06-15T10:30:00Z");
|
|
571
|
+
expect(reconstructed.sales_info_count).toBe(1);
|
|
572
|
+
expect(reconstructed.items_info_count).toBe(1);
|
|
573
|
+
});
|
|
574
|
+
it("returns profile selection when profileId is missing", async () => {
|
|
575
|
+
vi.mocked(resolveProfileMock).mockResolvedValue({
|
|
576
|
+
ok: false,
|
|
577
|
+
response: { selection_required: true }
|
|
578
|
+
});
|
|
579
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
580
|
+
periodKey: "2026-06-01",
|
|
581
|
+
scope: "ml",
|
|
582
|
+
documentType: "BILL"
|
|
583
|
+
});
|
|
584
|
+
expect(result).toEqual({ selection_required: true });
|
|
585
|
+
});
|
|
586
|
+
it("defaults documentType to BILL when omitted", async () => {
|
|
587
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
588
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })
|
|
589
|
+
]));
|
|
590
|
+
await meliGetBillingProvisionsSummaryHandler({
|
|
591
|
+
periodKey: "2026-06-01",
|
|
592
|
+
scope: "ml"
|
|
593
|
+
});
|
|
594
|
+
const call = axiosGetMock.mock.calls[0];
|
|
595
|
+
expect(call[1].params.document_type).toBe("BILL");
|
|
596
|
+
});
|
|
597
|
+
it("passes fromIdByScope cursor to the service layer", async () => {
|
|
598
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
599
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })
|
|
600
|
+
]));
|
|
601
|
+
await meliGetBillingProvisionsSummaryHandler({
|
|
602
|
+
periodKey: "2026-06-01",
|
|
603
|
+
scope: "ml",
|
|
604
|
+
documentType: "BILL",
|
|
605
|
+
fromIdByScope: { ml: "cursor-ml-99" }
|
|
606
|
+
});
|
|
607
|
+
const call = axiosGetMock.mock.calls[0];
|
|
608
|
+
expect(call[1].params.from_id).toBe("cursor-ml-99");
|
|
609
|
+
});
|
|
610
|
+
it("passes fromIdByScope for scope=all with per-scope cursors", async () => {
|
|
611
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
612
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })
|
|
613
|
+
]));
|
|
614
|
+
await meliGetBillingProvisionsSummaryHandler({
|
|
615
|
+
periodKey: "2026-06-01",
|
|
616
|
+
scope: "all",
|
|
617
|
+
documentType: "BILL",
|
|
618
|
+
fromIdByScope: {
|
|
619
|
+
ml: "ml-cursor-1",
|
|
620
|
+
mp: "mp-cursor-2"
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
const allCalls = axiosGetMock.mock.calls;
|
|
624
|
+
expect(allCalls[0][1].params.from_id).toBe("ml-cursor-1");
|
|
625
|
+
expect(allCalls[1][1].params.from_id).toBe("mp-cursor-2");
|
|
626
|
+
expect(allCalls[2][1].params.from_id).toBeUndefined();
|
|
627
|
+
expect(allCalls[3][1].params.from_id).toBeUndefined();
|
|
628
|
+
});
|
|
629
|
+
it("diagnostics reports observed detail_sub_types", async () => {
|
|
630
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
631
|
+
nestedRow({ charge_info: { detail_id: "1", detail_amount: 100, detail_sub_type: "CVFV" } }),
|
|
632
|
+
nestedRow({ charge_info: { detail_id: "2", detail_amount: 50, detail_sub_type: "CFF" } }),
|
|
633
|
+
nestedRow({ charge_info: { detail_id: "3", detail_amount: 30, detail_sub_type: "PADS" } })
|
|
634
|
+
]));
|
|
635
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
636
|
+
periodKey: "2026-06-01",
|
|
637
|
+
scope: "ml",
|
|
638
|
+
documentType: "BILL"
|
|
639
|
+
});
|
|
640
|
+
const payload = result.payload;
|
|
641
|
+
const diag = payload.diagnostics;
|
|
642
|
+
expect(diag.detail_sub_types_observed).toContain("CVFV");
|
|
643
|
+
expect(diag.detail_sub_types_observed).toContain("CFF");
|
|
644
|
+
expect(diag.detail_sub_types_observed).toContain("PADS");
|
|
645
|
+
expect(diag.detail_sub_types_observed).toHaveLength(3);
|
|
646
|
+
});
|
|
647
|
+
it("diagnostics reports observed marketplaces", async () => {
|
|
648
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
649
|
+
nestedRow({
|
|
650
|
+
charge_info: { detail_id: "1", detail_amount: 100, detail_sub_type: "CVFV" },
|
|
651
|
+
marketplace_info: { marketplace: "MCLICS" }
|
|
652
|
+
}),
|
|
653
|
+
nestedRow({
|
|
654
|
+
charge_info: { detail_id: "2", detail_amount: 50, detail_sub_type: "CFF" },
|
|
655
|
+
marketplace_info: { marketplace: "TAXES" }
|
|
656
|
+
})
|
|
657
|
+
]));
|
|
658
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
659
|
+
periodKey: "2026-06-01",
|
|
660
|
+
scope: "mp",
|
|
661
|
+
documentType: "BILL"
|
|
662
|
+
});
|
|
663
|
+
const payload = result.payload;
|
|
664
|
+
const diag = payload.diagnostics;
|
|
665
|
+
expect(diag.marketplaces_observed).toContain("MCLICS");
|
|
666
|
+
expect(diag.marketplaces_observed).toContain("TAXES");
|
|
667
|
+
expect(diag.marketplaces_observed).toHaveLength(2);
|
|
668
|
+
});
|
|
669
|
+
it("diagnostics reports observed concept_types", async () => {
|
|
670
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
671
|
+
nestedRow({ charge_info: { detail_id: "1", detail_amount: 100, detail_sub_type: "CVFV", concept_type: "sale" } }),
|
|
672
|
+
nestedRow({ charge_info: { detail_id: "2", detail_amount: 50, detail_sub_type: "CFF", concept_type: "shipping" } })
|
|
673
|
+
]));
|
|
674
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
675
|
+
periodKey: "2026-06-01",
|
|
676
|
+
scope: "ml",
|
|
677
|
+
documentType: "BILL"
|
|
678
|
+
});
|
|
679
|
+
const payload = result.payload;
|
|
680
|
+
const diag = payload.diagnostics;
|
|
681
|
+
expect(diag.concept_types_observed).toContain("sale");
|
|
682
|
+
expect(diag.concept_types_observed).toContain("shipping");
|
|
683
|
+
expect(diag.concept_types_observed).toHaveLength(2);
|
|
684
|
+
});
|
|
685
|
+
it("details_schema is exact and in correct order", async () => {
|
|
686
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
687
|
+
nestedRow({ charge_info: { detail_id: "d1", detail_amount: 10 } })
|
|
688
|
+
]));
|
|
689
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
690
|
+
periodKey: "2026-06-01",
|
|
691
|
+
scope: "ml",
|
|
692
|
+
documentType: "BILL"
|
|
693
|
+
});
|
|
694
|
+
const payload = result.payload;
|
|
695
|
+
expect(payload.details_schema).toEqual([
|
|
696
|
+
"detail_id",
|
|
697
|
+
"detail_type",
|
|
698
|
+
"detail_sub_type",
|
|
699
|
+
"transaction_detail",
|
|
700
|
+
"detail_amount",
|
|
701
|
+
"currency_id",
|
|
702
|
+
"marketplace",
|
|
703
|
+
"concept_type",
|
|
704
|
+
"order_id",
|
|
705
|
+
"operation_id",
|
|
706
|
+
"item_id",
|
|
707
|
+
"item_title",
|
|
708
|
+
"shipping_id",
|
|
709
|
+
"pack_id",
|
|
710
|
+
"document_id",
|
|
711
|
+
"creation_date_time",
|
|
712
|
+
"sales_info_count",
|
|
713
|
+
"items_info_count"
|
|
714
|
+
]);
|
|
715
|
+
});
|
|
716
|
+
it("empty details produces empty details array and zero summary with currency_totals and secondary blocks", async () => {
|
|
717
|
+
axiosGetMock.mockResolvedValue(mockApi([], null, 0));
|
|
718
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
719
|
+
periodKey: "2026-06-01",
|
|
720
|
+
scope: "ml",
|
|
721
|
+
documentType: "BILL"
|
|
722
|
+
});
|
|
723
|
+
const payload = result.payload;
|
|
724
|
+
const details = payload.details;
|
|
725
|
+
expect(details).toHaveLength(0);
|
|
726
|
+
const summary = payload.summary;
|
|
727
|
+
expect(summary.detail_count).toBe(0);
|
|
728
|
+
expect(summary.order_count_detected).toBe(0);
|
|
729
|
+
expect(summary.item_count_detected).toBe(0);
|
|
730
|
+
expect(summary.currency_totals).toBeDefined();
|
|
731
|
+
expect(summary.currency_totals).toEqual({});
|
|
732
|
+
expect(payload.details_sales).toEqual([]);
|
|
733
|
+
expect(payload.details_items).toEqual([]);
|
|
734
|
+
expect(payload.details_sales_schema).toEqual(detailsSalesSchema);
|
|
735
|
+
expect(payload.details_items_schema).toEqual(detailsItemsSchema);
|
|
736
|
+
const diag = payload.diagnostics;
|
|
737
|
+
expect(diag.detail_sub_types_observed).toHaveLength(0);
|
|
738
|
+
expect(diag.marketplaces_observed).toHaveLength(0);
|
|
739
|
+
expect(diag.concept_types_observed).toHaveLength(0);
|
|
740
|
+
});
|
|
741
|
+
it("details_schema includes sales_info_count and items_info_count", async () => {
|
|
742
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
743
|
+
nestedRow({
|
|
744
|
+
charge_info: { detail_id: "d1", detail_amount: 10 },
|
|
745
|
+
sales_info: [{ order_id: "O1" }, { order_id: "O2" }, { order_id: "O3" }],
|
|
746
|
+
items_info: [{ item_id: "I1" }, { item_id: "I2" }]
|
|
747
|
+
})
|
|
748
|
+
]));
|
|
749
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
750
|
+
periodKey: "2026-06-01",
|
|
751
|
+
scope: "ml",
|
|
752
|
+
documentType: "BILL"
|
|
753
|
+
});
|
|
754
|
+
const payload = result.payload;
|
|
755
|
+
const details = payload.details;
|
|
756
|
+
const row = details[0];
|
|
757
|
+
expect(row[16]).toBe(3);
|
|
758
|
+
expect(row[17]).toBe(2);
|
|
759
|
+
expect(row[8]).toBe("O1");
|
|
760
|
+
expect(row[10]).toBe("I1");
|
|
761
|
+
});
|
|
762
|
+
it("multiple sales_info produces secondary details_sales block with all entries", async () => {
|
|
763
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
764
|
+
nestedRow({
|
|
765
|
+
charge_info: { detail_id: "multi-sale", detail_amount: 100, detail_sub_type: "CVFV" },
|
|
766
|
+
sales_info: [
|
|
767
|
+
{ order_id: "ORD-A", operation_id: "OP-A" },
|
|
768
|
+
{ order_id: "ORD-B", operation_id: "OP-B" },
|
|
769
|
+
{ order_id: "ORD-C" }
|
|
770
|
+
]
|
|
771
|
+
})
|
|
772
|
+
]));
|
|
773
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
774
|
+
periodKey: "2026-06-01",
|
|
775
|
+
scope: "ml",
|
|
776
|
+
documentType: "BILL"
|
|
777
|
+
});
|
|
778
|
+
const payload = result.payload;
|
|
779
|
+
expect(payload.details_sales_schema).toEqual(detailsSalesSchema);
|
|
780
|
+
const salesRows = payload.details_sales;
|
|
781
|
+
expect(salesRows).toHaveLength(3);
|
|
782
|
+
for (const sr of salesRows) {
|
|
783
|
+
expect(sr[0]).toBe("multi-sale");
|
|
784
|
+
}
|
|
785
|
+
expect(salesRows[0][1]).toBe("ORD-A");
|
|
786
|
+
expect(salesRows[0][2]).toBe("OP-A");
|
|
787
|
+
expect(salesRows[1][1]).toBe("ORD-B");
|
|
788
|
+
expect(salesRows[1][2]).toBe("OP-B");
|
|
789
|
+
expect(salesRows[2][1]).toBe("ORD-C");
|
|
790
|
+
expect(salesRows[2][2]).toBeNull();
|
|
791
|
+
const summary = payload.summary;
|
|
792
|
+
expect(summary.order_count_detected).toBe(3);
|
|
793
|
+
});
|
|
794
|
+
it("multiple items_info produces secondary details_items block with all entries", async () => {
|
|
795
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
796
|
+
nestedRow({
|
|
797
|
+
charge_info: { detail_id: "multi-item", detail_amount: 200, detail_sub_type: "CVFV" },
|
|
798
|
+
items_info: [
|
|
799
|
+
{ item_id: "ITEM-1", item_title: "Producto A" },
|
|
800
|
+
{ item_id: "ITEM-2", item_title: "Producto B" },
|
|
801
|
+
{ item_id: "ITEM-3", item_title: "Producto C" },
|
|
802
|
+
{ item_id: "ITEM-4" }
|
|
803
|
+
]
|
|
804
|
+
})
|
|
805
|
+
]));
|
|
806
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
807
|
+
periodKey: "2026-06-01",
|
|
808
|
+
scope: "ml",
|
|
809
|
+
documentType: "BILL"
|
|
810
|
+
});
|
|
811
|
+
const payload = result.payload;
|
|
812
|
+
expect(payload.details_items_schema).toEqual(detailsItemsSchema);
|
|
813
|
+
const itemsRows = payload.details_items;
|
|
814
|
+
expect(itemsRows).toHaveLength(4);
|
|
815
|
+
for (const ir of itemsRows) {
|
|
816
|
+
expect(ir[0]).toBe("multi-item");
|
|
817
|
+
}
|
|
818
|
+
expect(itemsRows[0][1]).toBe("ITEM-1");
|
|
819
|
+
expect(itemsRows[0][2]).toBe("Producto A");
|
|
820
|
+
expect(itemsRows[3][1]).toBe("ITEM-4");
|
|
821
|
+
expect(itemsRows[3][2]).toBeNull();
|
|
822
|
+
const summary = payload.summary;
|
|
823
|
+
expect(summary.item_count_detected).toBe(4);
|
|
824
|
+
});
|
|
825
|
+
it("preserves null values at correct positions without column shift", async () => {
|
|
826
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
827
|
+
nestedRow({
|
|
828
|
+
charge_info: {
|
|
829
|
+
detail_id: "null-test",
|
|
830
|
+
detail_amount: 0
|
|
831
|
+
// detail_type, detail_sub_type, transaction_detail, concept_type, creation_date_time all missing
|
|
832
|
+
},
|
|
833
|
+
currency_info: {},
|
|
834
|
+
// no currency_id
|
|
835
|
+
marketplace_info: {},
|
|
836
|
+
// no marketplace
|
|
837
|
+
sales_info: [],
|
|
838
|
+
// no sales
|
|
839
|
+
items_info: []
|
|
840
|
+
// no items
|
|
841
|
+
// document_id missing
|
|
842
|
+
})
|
|
843
|
+
]));
|
|
844
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
845
|
+
periodKey: "2026-06-01",
|
|
846
|
+
scope: "ml",
|
|
847
|
+
documentType: "BILL"
|
|
848
|
+
});
|
|
849
|
+
const payload = result.payload;
|
|
850
|
+
const details = payload.details;
|
|
851
|
+
const row = details[0];
|
|
852
|
+
expect(row[0]).toBe("null-test");
|
|
853
|
+
expect(row[1]).toBeNull();
|
|
854
|
+
expect(row[2]).toBeNull();
|
|
855
|
+
expect(row[3]).toBeNull();
|
|
856
|
+
expect(row[4]).toBe(0);
|
|
857
|
+
expect(row[5]).toBeNull();
|
|
858
|
+
expect(row[6]).toBeNull();
|
|
859
|
+
expect(row[7]).toBeNull();
|
|
860
|
+
expect(row[8]).toBeNull();
|
|
861
|
+
expect(row[9]).toBeNull();
|
|
862
|
+
expect(row[10]).toBeNull();
|
|
863
|
+
expect(row[11]).toBeNull();
|
|
864
|
+
expect(row[12]).toBeNull();
|
|
865
|
+
expect(row[13]).toBeNull();
|
|
866
|
+
expect(row[14]).toBeNull();
|
|
867
|
+
expect(row[15]).toBeNull();
|
|
868
|
+
expect(row[16]).toBe(0);
|
|
869
|
+
expect(row[17]).toBe(0);
|
|
870
|
+
expect(row).toHaveLength(detailsSchema.length);
|
|
871
|
+
const salesRows = payload.details_sales;
|
|
872
|
+
const itemsRows = payload.details_items;
|
|
873
|
+
expect(salesRows).toHaveLength(0);
|
|
874
|
+
expect(itemsRows).toHaveLength(0);
|
|
875
|
+
});
|
|
876
|
+
it("secondary blocks populated even with single sale/item (contract stability)", async () => {
|
|
877
|
+
axiosGetMock.mockResolvedValue(mockApi([
|
|
878
|
+
nestedRow({
|
|
879
|
+
charge_info: { detail_id: "single-1", detail_amount: 50, detail_sub_type: "CVFV" },
|
|
880
|
+
sales_info: [{ order_id: "O-SINGLE" }],
|
|
881
|
+
items_info: [{ item_id: "I-SINGLE", item_title: "Solo" }]
|
|
882
|
+
})
|
|
883
|
+
]));
|
|
884
|
+
const result = await meliGetBillingProvisionsSummaryHandler({
|
|
885
|
+
periodKey: "2026-06-01",
|
|
886
|
+
scope: "ml",
|
|
887
|
+
documentType: "BILL"
|
|
888
|
+
});
|
|
889
|
+
const payload = result.payload;
|
|
890
|
+
const salesRows = payload.details_sales;
|
|
891
|
+
const itemsRows = payload.details_items;
|
|
892
|
+
expect(salesRows).toHaveLength(1);
|
|
893
|
+
expect(itemsRows).toHaveLength(1);
|
|
894
|
+
expect(salesRows[0][0]).toBe("single-1");
|
|
895
|
+
expect(salesRows[0][1]).toBe("O-SINGLE");
|
|
896
|
+
expect(itemsRows[0][0]).toBe("single-1");
|
|
897
|
+
expect(itemsRows[0][1]).toBe("I-SINGLE");
|
|
898
|
+
expect(itemsRows[0][2]).toBe("Solo");
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
describe("meliGetBillingProvisionsSummarySchema validation", () => {
|
|
902
|
+
it("requires profileId and periodKey and scope", () => {
|
|
903
|
+
const result = meliGetBillingProvisionsSummarySchema.safeParse({});
|
|
904
|
+
expect(result.success).toBe(false);
|
|
905
|
+
expect(result.error?.issues.length).toBeGreaterThanOrEqual(2);
|
|
906
|
+
});
|
|
907
|
+
it("accepts full valid payload including fromIdByScope", () => {
|
|
908
|
+
const result = meliGetBillingProvisionsSummarySchema.safeParse({
|
|
909
|
+
profileId: "my-profile",
|
|
910
|
+
periodKey: "2025-01-01",
|
|
911
|
+
scope: "all",
|
|
912
|
+
documentType: "CREDIT_NOTE",
|
|
913
|
+
detailType: "sale",
|
|
914
|
+
detailSubTypes: ["commission"],
|
|
915
|
+
orderIds: ["O123"],
|
|
916
|
+
itemIds: ["I456"],
|
|
917
|
+
fromIdByScope: { ml: "abc-123", mp: "def-456" }
|
|
918
|
+
});
|
|
919
|
+
expect(result.success).toBe(true);
|
|
920
|
+
});
|
|
921
|
+
it("rejects non-string values in fromIdByScope keys", () => {
|
|
922
|
+
const result = meliGetBillingProvisionsSummarySchema.safeParse({
|
|
923
|
+
profileId: "my-profile",
|
|
924
|
+
periodKey: "2025-01-01",
|
|
925
|
+
scope: "ml",
|
|
926
|
+
fromIdByScope: { ml: 123 }
|
|
927
|
+
});
|
|
928
|
+
expect(result.success).toBe(false);
|
|
929
|
+
});
|
|
930
|
+
it("accepts empty fromIdByScope", () => {
|
|
931
|
+
const result = meliGetBillingProvisionsSummarySchema.safeParse({
|
|
932
|
+
profileId: "my-profile",
|
|
933
|
+
periodKey: "2025-01-01",
|
|
934
|
+
scope: "ml",
|
|
935
|
+
fromIdByScope: {}
|
|
936
|
+
});
|
|
937
|
+
expect(result.success).toBe(true);
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
//# sourceMappingURL=mercadolibre-billing-provisions.test.js.map
|