@yoryoboy/bi-mcp 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +398 -8
- package/dist/index.js.map +2 -2
- package/dist/mcp-use.json +2 -2
- package/dist/src/config/__tests__/meta.test.js +35 -0
- package/dist/src/config/__tests__/meta.test.js.map +7 -0
- package/dist/src/config/meta-store.js +109 -0
- package/dist/src/config/meta-store.js.map +7 -0
- package/dist/src/config/meta.js +0 -2
- package/dist/src/config/meta.js.map +2 -2
- package/dist/src/meta/__tests__/meta-utils.test.js +155 -0
- package/dist/src/meta/__tests__/meta-utils.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js +265 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-items.test.js +311 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-items.test.js.map +7 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js +220 -0
- package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js.map +7 -0
- package/dist/src/services/meta/__tests__/meta-ads.test.js +126 -0
- package/dist/src/services/meta/__tests__/meta-ads.test.js.map +7 -0
- package/dist/src/services/meta/__tests__/meta-api.test.js +70 -0
- package/dist/src/services/meta/__tests__/meta-api.test.js.map +7 -0
- package/dist/src/services/meta/meta-ads.js +94 -40
- package/dist/src/services/meta/meta-ads.js.map +2 -2
- package/dist/src/services/meta/meta-api.js +8 -6
- package/dist/src/services/meta/meta-api.js.map +2 -2
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js +165 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-eans.test.js +92 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-eans.test.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-products.test.js +41 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog-write-products.test.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog.test.js +80 -0
- package/dist/src/services/vtex/__tests__/vtex-catalog.test.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-orders-write-http.test.js +85 -0
- package/dist/src/services/vtex/__tests__/vtex-orders-write-http.test.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-orders-write-state-validation.test.js +251 -0
- package/dist/src/services/vtex/__tests__/vtex-orders-write-state-validation.test.js.map +7 -0
- package/dist/src/services/vtex/__tests__/vtex-write.test.js +111 -0
- package/dist/src/services/vtex/__tests__/vtex-write.test.js.map +7 -0
- package/dist/src/services/vtex/vtex-catalog-write.js +194 -1
- package/dist/src/services/vtex/vtex-catalog-write.js.map +2 -2
- package/dist/src/services/vtex/vtex-catalog.js +118 -1
- package/dist/src/services/vtex/vtex-catalog.js.map +2 -2
- package/dist/src/services/vtex/vtex-pricing-write.js +5 -0
- package/dist/src/services/vtex/vtex-pricing-write.js.map +2 -2
- package/dist/src/services/vtex/vtex-write.js +67 -1
- package/dist/src/services/vtex/vtex-write.js.map +2 -2
- package/dist/src/tools/config/list-profiles.js +37 -6
- package/dist/src/tools/config/list-profiles.js.map +2 -2
- package/dist/src/tools/index.js.map +2 -2
- package/dist/src/tools/mercadolibre/__tests__/helpers.test.js +37 -0
- package/dist/src/tools/mercadolibre/__tests__/helpers.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/__tests__/profile-resolution.test.js +30 -0
- package/dist/src/tools/mercadolibre/__tests__/profile-resolution.test.js.map +7 -0
- package/dist/src/tools/mercadolibre/profile-resolution.js +4 -50
- package/dist/src/tools/mercadolibre/profile-resolution.js.map +2 -2
- package/dist/src/tools/meta/__tests__/pagination.test.js +133 -0
- package/dist/src/tools/meta/__tests__/pagination.test.js.map +7 -0
- package/dist/src/tools/meta/__tests__/profile-access.test.js +262 -0
- package/dist/src/tools/meta/__tests__/profile-access.test.js.map +7 -0
- package/dist/src/tools/meta/__tests__/read-tools.test.js +722 -0
- package/dist/src/tools/meta/__tests__/read-tools.test.js.map +7 -0
- package/dist/src/tools/meta/__tests__/schemas.test.js +103 -0
- package/dist/src/tools/meta/__tests__/schemas.test.js.map +7 -0
- package/dist/src/tools/meta/account-overview.js +37 -19
- package/dist/src/tools/meta/account-overview.js.map +2 -2
- package/dist/src/tools/meta/ad-account-info.js +31 -6
- package/dist/src/tools/meta/ad-account-info.js.map +2 -2
- package/dist/src/tools/meta/ads-performance.js +35 -21
- package/dist/src/tools/meta/ads-performance.js.map +2 -2
- package/dist/src/tools/meta/campaign-performance.js +35 -18
- package/dist/src/tools/meta/campaign-performance.js.map +2 -2
- package/dist/src/tools/meta/list-accessible-ad-accounts.js +39 -10
- package/dist/src/tools/meta/list-accessible-ad-accounts.js.map +2 -2
- package/dist/src/tools/meta/list-accessible-businesses.js +37 -13
- package/dist/src/tools/meta/list-accessible-businesses.js.map +2 -2
- package/dist/src/tools/meta/placement-mix.js +37 -22
- package/dist/src/tools/meta/placement-mix.js.map +2 -2
- package/dist/src/tools/meta/profile-access.js +215 -0
- package/dist/src/tools/meta/profile-access.js.map +7 -0
- package/dist/src/tools/meta/schema-helpers.js +19 -0
- package/dist/src/tools/meta/schema-helpers.js.map +7 -0
- package/dist/src/tools/meta/time-series.js +40 -23
- package/dist/src/tools/meta/time-series.js.map +2 -2
- package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js +233 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js.map +7 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-categories.test.js +649 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-categories.test.js.map +7 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-product-specifications.test.js +339 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-product-specifications.test.js.map +7 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-products-skus.test.js +235 -0
- package/dist/src/tools/vtex/__tests__/catalog-admin-products-skus.test.js.map +7 -0
- package/dist/src/tools/vtex/__tests__/catalog-navigation-reads.test.js +372 -0
- package/dist/src/tools/vtex/__tests__/catalog-navigation-reads.test.js.map +7 -0
- package/dist/src/tools/vtex/__tests__/catalog-navigation-surface.test.js +57 -0
- package/dist/src/tools/vtex/__tests__/catalog-navigation-surface.test.js.map +7 -0
- package/dist/src/tools/vtex/__tests__/write-helpers.test.js +97 -0
- package/dist/src/tools/vtex/__tests__/write-helpers.test.js.map +7 -0
- package/dist/src/tools/vtex/activate-sku.js +1 -48
- package/dist/src/tools/vtex/activate-sku.js.map +2 -2
- package/dist/src/tools/vtex/associate-specification.js +3 -54
- package/dist/src/tools/vtex/associate-specification.js.map +2 -2
- package/dist/src/tools/vtex/attach-catalog-image.js +3 -57
- package/dist/src/tools/vtex/attach-catalog-image.js.map +2 -2
- package/dist/src/tools/vtex/catalog-admin-batch.js +298 -0
- package/dist/src/tools/vtex/catalog-admin-batch.js.map +7 -0
- package/dist/src/tools/vtex/catalog-admin-categories.js +542 -0
- package/dist/src/tools/vtex/catalog-admin-categories.js.map +7 -0
- package/dist/src/tools/vtex/catalog-admin-product-specifications.js +275 -0
- package/dist/src/tools/vtex/catalog-admin-product-specifications.js.map +7 -0
- package/dist/src/tools/vtex/catalog-admin-products-skus.js +475 -0
- package/dist/src/tools/vtex/catalog-admin-products-skus.js.map +7 -0
- package/dist/src/tools/vtex/catalog-navigation-reads.js +430 -0
- package/dist/src/tools/vtex/catalog-navigation-reads.js.map +7 -0
- package/dist/src/tools/vtex/create-brand.js +1 -64
- package/dist/src/tools/vtex/create-brand.js.map +2 -2
- package/dist/src/tools/vtex/create-category.js +1 -76
- package/dist/src/tools/vtex/create-category.js.map +2 -2
- package/dist/src/tools/vtex/create-product-with-sku.js +3 -114
- package/dist/src/tools/vtex/create-product-with-sku.js.map +2 -2
- package/dist/src/tools/vtex/create-specification-value.js +3 -47
- package/dist/src/tools/vtex/create-specification-value.js.map +2 -2
- package/dist/src/tools/vtex/create-specification.js +1 -80
- package/dist/src/tools/vtex/create-specification.js.map +2 -2
- package/dist/src/tools/vtex/deactivate-sku.js +1 -48
- package/dist/src/tools/vtex/deactivate-sku.js.map +2 -2
- package/dist/src/tools/vtex/delete-all-product-specifications.js +9 -0
- package/dist/src/tools/vtex/delete-all-product-specifications.js.map +7 -0
- package/dist/src/tools/vtex/delete-all-sku-specifications.js +9 -0
- package/dist/src/tools/vtex/delete-all-sku-specifications.js.map +7 -0
- package/dist/src/tools/vtex/delete-brand.js +9 -0
- package/dist/src/tools/vtex/delete-brand.js.map +7 -0
- package/dist/src/tools/vtex/delete-catalog-image.js +9 -0
- package/dist/src/tools/vtex/delete-catalog-image.js.map +7 -0
- package/dist/src/tools/vtex/delete-product-specification.js +9 -0
- package/dist/src/tools/vtex/delete-product-specification.js.map +7 -0
- package/dist/src/tools/vtex/delete-sku-price.js +55 -0
- package/dist/src/tools/vtex/delete-sku-price.js.map +7 -0
- package/dist/src/tools/vtex/delete-sku-specification.js +9 -0
- package/dist/src/tools/vtex/delete-sku-specification.js.map +7 -0
- package/dist/src/tools/vtex/get-brand.js +6 -0
- package/dist/src/tools/vtex/get-brand.js.map +7 -0
- package/dist/src/tools/vtex/get-category-tree.js +6 -0
- package/dist/src/tools/vtex/get-category-tree.js.map +7 -0
- package/dist/src/tools/vtex/get-category.js +6 -0
- package/dist/src/tools/vtex/get-category.js.map +7 -0
- package/dist/src/tools/vtex/get-product-specifications.js +9 -0
- package/dist/src/tools/vtex/get-product-specifications.js.map +7 -0
- package/dist/src/tools/vtex/get-product.js +6 -0
- package/dist/src/tools/vtex/get-product.js.map +7 -0
- package/dist/src/tools/vtex/get-sku.js +6 -0
- package/dist/src/tools/vtex/get-sku.js.map +7 -0
- package/dist/src/tools/vtex/index.js +23 -1
- package/dist/src/tools/vtex/index.js.map +2 -2
- package/dist/src/tools/vtex/list-brands.js +6 -0
- package/dist/src/tools/vtex/list-brands.js.map +7 -0
- package/dist/src/tools/vtex/list-categories.js +6 -0
- package/dist/src/tools/vtex/list-categories.js.map +7 -0
- package/dist/src/tools/vtex/list-products-by-category.js +6 -0
- package/dist/src/tools/vtex/list-products-by-category.js.map +7 -0
- package/dist/src/tools/vtex/list-products.js +6 -0
- package/dist/src/tools/vtex/list-products.js.map +7 -0
- package/dist/src/tools/vtex/list-skus-by-product.js +6 -0
- package/dist/src/tools/vtex/list-skus-by-product.js.map +7 -0
- package/dist/src/tools/vtex/list-specification-groups.js +9 -0
- package/dist/src/tools/vtex/list-specification-groups.js.map +7 -0
- package/dist/src/tools/vtex/move-category.js +6 -0
- package/dist/src/tools/vtex/move-category.js.map +7 -0
- package/dist/src/tools/vtex/profile-resolution.js +4 -51
- package/dist/src/tools/vtex/profile-resolution.js.map +2 -2
- package/dist/src/tools/vtex/update-brand.js +6 -0
- package/dist/src/tools/vtex/update-brand.js.map +7 -0
- package/dist/src/tools/vtex/update-category.js +6 -0
- package/dist/src/tools/vtex/update-category.js.map +7 -0
- package/dist/src/tools/vtex/update-product-basic-fields.js +3 -65
- package/dist/src/tools/vtex/update-product-basic-fields.js.map +2 -2
- package/dist/src/tools/vtex/update-sku-basic-fields.js +1 -87
- package/dist/src/tools/vtex/update-sku-basic-fields.js.map +2 -2
- package/dist/src/tools/vtex/update-sku-price.js +21 -1
- package/dist/src/tools/vtex/update-sku-price.js.map +2 -2
- package/dist/src/tools/vtex/write-helpers.js +104 -14
- package/dist/src/tools/vtex/write-helpers.js.map +2 -2
- package/dist/src/utils/provider-profile-selection.js +117 -0
- package/dist/src/utils/provider-profile-selection.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js +678 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js +564 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js +387 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js +368 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js +626 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js.map +7 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-write-batch8.test.js +480 -0
- package/dist/tests/meli/mercadolibre-tool-handlers-write-batch8.test.js.map +7 -0
- package/dist/tests/setup.js +2 -0
- package/dist/tests/setup.js.map +7 -0
- package/dist/tests/smoke/test-harness.test.js +7 -0
- package/dist/tests/smoke/test-harness.test.js.map +7 -0
- package/dist/tests/vtex/read-only-utils.test.js +161 -0
- package/dist/tests/vtex/read-only-utils.test.js.map +7 -0
- package/dist/tests/vtex/vtex-catalog-admin-docs.test.js +24 -0
- package/dist/tests/vtex/vtex-catalog-admin-docs.test.js.map +7 -0
- package/dist/tests/vtex/vtex-catalog-admin-surface.test.js +30 -0
- package/dist/tests/vtex/vtex-catalog-admin-surface.test.js.map +7 -0
- package/dist/tests/vtex/vtex-catalog-write-tools.test.js +712 -0
- package/dist/tests/vtex/vtex-catalog-write-tools.test.js.map +7 -0
- package/dist/tests/vtex/vtex-catalog.service.test.js +51 -0
- package/dist/tests/vtex/vtex-catalog.service.test.js.map +7 -0
- package/dist/tests/vtex/vtex-inventory-tools.test.js +201 -0
- package/dist/tests/vtex/vtex-inventory-tools.test.js.map +7 -0
- package/dist/tests/vtex/vtex-logistics.service.test.js +134 -0
- package/dist/tests/vtex/vtex-logistics.service.test.js.map +7 -0
- package/dist/tests/vtex/vtex-order-details.tool.test.js +141 -0
- package/dist/tests/vtex/vtex-order-details.tool.test.js.map +7 -0
- package/dist/tests/vtex/vtex-order-write-tools.test.js +483 -0
- package/dist/tests/vtex/vtex-order-write-tools.test.js.map +7 -0
- package/dist/tests/vtex/vtex-orders-summary.tool.test.js +185 -0
- package/dist/tests/vtex/vtex-orders-summary.tool.test.js.map +7 -0
- package/dist/tests/vtex/vtex-orders.service.test.js +120 -0
- package/dist/tests/vtex/vtex-orders.service.test.js.map +7 -0
- package/dist/tests/vtex/vtex-pricing-write-tools.test.js +202 -0
- package/dist/tests/vtex/vtex-pricing-write-tools.test.js.map +7 -0
- package/dist/tests/vtex/vtex-pricing-write.service.test.js +106 -0
- package/dist/tests/vtex/vtex-pricing-write.service.test.js.map +7 -0
- package/dist/tests/vtex/vtex-pricing.service.test.js +88 -0
- package/dist/tests/vtex/vtex-pricing.service.test.js.map +7 -0
- package/dist/tests/vtex/vtex-small-tools.test.js +190 -0
- package/dist/tests/vtex/vtex-small-tools.test.js.map +7 -0
- package/dist/tests/vtex/vtex-write-simple-tools.test.js +647 -0
- package/dist/tests/vtex/vtex-write-simple-tools.test.js.map +7 -0
- package/dist/vitest.config.js +15 -0
- package/dist/vitest.config.js.map +7 -0
- package/package.json +6 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
aggregateInsightsByCurrency,
|
|
4
|
+
getActionValue,
|
|
5
|
+
getMetaAdAccountStatusLabel,
|
|
6
|
+
getMetricBundle,
|
|
7
|
+
normalizeMetaAdAccountId,
|
|
8
|
+
normalizeRequestedAccountIds,
|
|
9
|
+
parseNumber,
|
|
10
|
+
resolveRequestedAccounts,
|
|
11
|
+
round,
|
|
12
|
+
toActId,
|
|
13
|
+
toPercent
|
|
14
|
+
} from "../meta-utils.js";
|
|
15
|
+
describe("meta utils", () => {
|
|
16
|
+
it("normalizes Meta ad account ids and rejects invalid values", () => {
|
|
17
|
+
expect(normalizeMetaAdAccountId(" act_12345 ")).toBe("12345");
|
|
18
|
+
expect(toActId("12345")).toBe("act_12345");
|
|
19
|
+
expect(() => normalizeMetaAdAccountId("act_12A")).toThrow(
|
|
20
|
+
"Invalid Meta ad account ID. Use digits only, with or without the act_ prefix."
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
it("parses numbers, rounds values and guards percent math", () => {
|
|
24
|
+
expect(parseNumber("12.45")).toBe(12.45);
|
|
25
|
+
expect(parseNumber("nope")).toBe(0);
|
|
26
|
+
expect(parseNumber(void 0)).toBe(0);
|
|
27
|
+
expect(round(12.345)).toBe(12.35);
|
|
28
|
+
expect(round(Number.POSITIVE_INFINITY)).toBe(0);
|
|
29
|
+
expect(toPercent(4, 8)).toBe(50);
|
|
30
|
+
expect(toPercent(4, 0)).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
it("extracts action values and builds metric bundles with ROAS fallback", () => {
|
|
33
|
+
expect(
|
|
34
|
+
getActionValue(
|
|
35
|
+
[
|
|
36
|
+
{ action_type: "purchase", value: "3" },
|
|
37
|
+
{ action_type: "lead", value: "7" }
|
|
38
|
+
],
|
|
39
|
+
"purchase"
|
|
40
|
+
)
|
|
41
|
+
).toBe(3);
|
|
42
|
+
expect(
|
|
43
|
+
getMetricBundle({
|
|
44
|
+
spend: "25.125",
|
|
45
|
+
impressions: "1000",
|
|
46
|
+
clicks: "40",
|
|
47
|
+
reach: "700",
|
|
48
|
+
ctr: "4",
|
|
49
|
+
cpc: "0.628125",
|
|
50
|
+
cpm: "25.125",
|
|
51
|
+
actions: [{ action_type: "purchase", value: "2" }],
|
|
52
|
+
purchase_roas: [{ action_type: "omni_purchase", value: "3" }]
|
|
53
|
+
})
|
|
54
|
+
).toEqual({
|
|
55
|
+
spend: 25.13,
|
|
56
|
+
impressions: 1e3,
|
|
57
|
+
clicks: 40,
|
|
58
|
+
reach: 700,
|
|
59
|
+
purchases: 2,
|
|
60
|
+
purchase_value: 75.38,
|
|
61
|
+
ctr_percent: 4,
|
|
62
|
+
cpc: 0.63,
|
|
63
|
+
cpm: 25.13,
|
|
64
|
+
roas: 3,
|
|
65
|
+
conversion_rate_percent: 5
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it("aggregates insights by currency without unsafe cross-currency totals", () => {
|
|
69
|
+
expect(
|
|
70
|
+
aggregateInsightsByCurrency([
|
|
71
|
+
{
|
|
72
|
+
currencyCode: "ARS",
|
|
73
|
+
insight: {
|
|
74
|
+
spend: "10",
|
|
75
|
+
impressions: "100",
|
|
76
|
+
clicks: "5",
|
|
77
|
+
reach: "80",
|
|
78
|
+
actions: [{ action_type: "purchase", value: "1" }],
|
|
79
|
+
action_values: [{ action_type: "purchase", value: "20" }]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
currencyCode: "ARS",
|
|
84
|
+
insight: {
|
|
85
|
+
spend: "15.5",
|
|
86
|
+
impressions: "150",
|
|
87
|
+
clicks: "10",
|
|
88
|
+
reach: "120",
|
|
89
|
+
actions: [{ action_type: "purchase", value: "2" }],
|
|
90
|
+
action_values: [{ action_type: "purchase", value: "40" }]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
currencyCode: "USD",
|
|
95
|
+
insight: {
|
|
96
|
+
spend: "5",
|
|
97
|
+
impressions: "50",
|
|
98
|
+
clicks: "1",
|
|
99
|
+
reach: "45",
|
|
100
|
+
actions: [{ action_type: "purchase", value: "0" }],
|
|
101
|
+
action_values: [{ action_type: "purchase", value: "0" }]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
])
|
|
105
|
+
).toEqual([
|
|
106
|
+
{
|
|
107
|
+
currency_code: "ARS",
|
|
108
|
+
spend: 25.5,
|
|
109
|
+
purchases: 3,
|
|
110
|
+
purchase_value: 60,
|
|
111
|
+
impressions: 250,
|
|
112
|
+
clicks: 15,
|
|
113
|
+
reach: 200,
|
|
114
|
+
ctr_percent: 6,
|
|
115
|
+
cpc: 1.7,
|
|
116
|
+
cpm: 102,
|
|
117
|
+
roas: 2.35
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
currency_code: "USD",
|
|
121
|
+
spend: 5,
|
|
122
|
+
purchases: 0,
|
|
123
|
+
purchase_value: 0,
|
|
124
|
+
impressions: 50,
|
|
125
|
+
clicks: 1,
|
|
126
|
+
reach: 45,
|
|
127
|
+
ctr_percent: 2,
|
|
128
|
+
cpc: 5,
|
|
129
|
+
cpm: 100,
|
|
130
|
+
roas: 0
|
|
131
|
+
}
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
it("normalizes requested ids, resolves accessible accounts and exposes status labels", () => {
|
|
135
|
+
expect(normalizeRequestedAccountIds(["act_123", "123", " 456 "])).toEqual([
|
|
136
|
+
"123",
|
|
137
|
+
"456"
|
|
138
|
+
]);
|
|
139
|
+
expect(normalizeRequestedAccountIds([])).toBeUndefined();
|
|
140
|
+
expect(getMetaAdAccountStatusLabel(1)).toBe("active");
|
|
141
|
+
expect(getMetaAdAccountStatusLabel(9)).toBeUndefined();
|
|
142
|
+
const accessibleAccounts = [
|
|
143
|
+
{ id: "act_123", account_id: "123", name: "Main" },
|
|
144
|
+
{ id: "act_456", account_id: "456", name: "Secondary" }
|
|
145
|
+
];
|
|
146
|
+
expect(resolveRequestedAccounts(void 0, accessibleAccounts)).toEqual(accessibleAccounts);
|
|
147
|
+
expect(resolveRequestedAccounts(["456"], accessibleAccounts)).toEqual([
|
|
148
|
+
{ id: "act_456", account_id: "456", name: "Secondary" }
|
|
149
|
+
]);
|
|
150
|
+
expect(() => resolveRequestedAccounts(["999"], accessibleAccounts)).toThrow(
|
|
151
|
+
"None of the requested Meta ad accounts are accessible with the configured token."
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=meta-utils.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/meta/__tests__/meta-utils.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { describe, expect, it } from \"vitest\";\n\nimport {\n aggregateInsightsByCurrency,\n getActionValue,\n getMetaAdAccountStatusLabel,\n getMetricBundle,\n normalizeMetaAdAccountId,\n normalizeRequestedAccountIds,\n parseNumber,\n resolveRequestedAccounts,\n round,\n toActId,\n toPercent,\n} from \"../meta-utils.js\";\n\ndescribe(\"meta utils\", () => {\n it(\"normalizes Meta ad account ids and rejects invalid values\", () => {\n expect(normalizeMetaAdAccountId(\" act_12345 \")).toBe(\"12345\");\n expect(toActId(\"12345\")).toBe(\"act_12345\");\n expect(() => normalizeMetaAdAccountId(\"act_12A\")).toThrow(\n \"Invalid Meta ad account ID. Use digits only, with or without the act_ prefix.\"\n );\n });\n\n it(\"parses numbers, rounds values and guards percent math\", () => {\n expect(parseNumber(\"12.45\")).toBe(12.45);\n expect(parseNumber(\"nope\")).toBe(0);\n expect(parseNumber(undefined)).toBe(0);\n expect(round(12.345)).toBe(12.35);\n expect(round(Number.POSITIVE_INFINITY)).toBe(0);\n expect(toPercent(4, 8)).toBe(50);\n expect(toPercent(4, 0)).toBe(0);\n });\n\n it(\"extracts action values and builds metric bundles with ROAS fallback\", () => {\n expect(\n getActionValue(\n [\n { action_type: \"purchase\", value: \"3\" },\n { action_type: \"lead\", value: \"7\" },\n ],\n \"purchase\"\n )\n ).toBe(3);\n\n expect(\n getMetricBundle({\n spend: \"25.125\",\n impressions: \"1000\",\n clicks: \"40\",\n reach: \"700\",\n ctr: \"4\",\n cpc: \"0.628125\",\n cpm: \"25.125\",\n actions: [{ action_type: \"purchase\", value: \"2\" }],\n purchase_roas: [{ action_type: \"omni_purchase\", value: \"3\" }],\n })\n ).toEqual({\n spend: 25.13,\n impressions: 1000,\n clicks: 40,\n reach: 700,\n purchases: 2,\n purchase_value: 75.38,\n ctr_percent: 4,\n cpc: 0.63,\n cpm: 25.13,\n roas: 3,\n conversion_rate_percent: 5,\n });\n });\n\n it(\"aggregates insights by currency without unsafe cross-currency totals\", () => {\n expect(\n aggregateInsightsByCurrency([\n {\n currencyCode: \"ARS\",\n insight: {\n spend: \"10\",\n impressions: \"100\",\n clicks: \"5\",\n reach: \"80\",\n actions: [{ action_type: \"purchase\", value: \"1\" }],\n action_values: [{ action_type: \"purchase\", value: \"20\" }],\n },\n },\n {\n currencyCode: \"ARS\",\n insight: {\n spend: \"15.5\",\n impressions: \"150\",\n clicks: \"10\",\n reach: \"120\",\n actions: [{ action_type: \"purchase\", value: \"2\" }],\n action_values: [{ action_type: \"purchase\", value: \"40\" }],\n },\n },\n {\n currencyCode: \"USD\",\n insight: {\n spend: \"5\",\n impressions: \"50\",\n clicks: \"1\",\n reach: \"45\",\n actions: [{ action_type: \"purchase\", value: \"0\" }],\n action_values: [{ action_type: \"purchase\", value: \"0\" }],\n },\n },\n ])\n ).toEqual([\n {\n currency_code: \"ARS\",\n spend: 25.5,\n purchases: 3,\n purchase_value: 60,\n impressions: 250,\n clicks: 15,\n reach: 200,\n ctr_percent: 6,\n cpc: 1.7,\n cpm: 102,\n roas: 2.35,\n },\n {\n currency_code: \"USD\",\n spend: 5,\n purchases: 0,\n purchase_value: 0,\n impressions: 50,\n clicks: 1,\n reach: 45,\n ctr_percent: 2,\n cpc: 5,\n cpm: 100,\n roas: 0,\n },\n ]);\n });\n\n it(\"normalizes requested ids, resolves accessible accounts and exposes status labels\", () => {\n expect(normalizeRequestedAccountIds([\"act_123\", \"123\", \" 456 \"])).toEqual([\n \"123\",\n \"456\",\n ]);\n expect(normalizeRequestedAccountIds([])).toBeUndefined();\n expect(getMetaAdAccountStatusLabel(1)).toBe(\"active\");\n expect(getMetaAdAccountStatusLabel(9)).toBeUndefined();\n\n const accessibleAccounts = [\n { id: \"act_123\", account_id: \"123\", name: \"Main\" },\n { id: \"act_456\", account_id: \"456\", name: \"Secondary\" },\n ];\n\n expect(resolveRequestedAccounts(undefined, accessibleAccounts)).toEqual(accessibleAccounts);\n expect(resolveRequestedAccounts([\"456\"], accessibleAccounts)).toEqual([\n { id: \"act_456\", account_id: \"456\", name: \"Secondary\" },\n ]);\n expect(() => resolveRequestedAccounts([\"999\"], accessibleAccounts)).toThrow(\n \"None of the requested Meta ad accounts are accessible with the configured token.\"\n );\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { getMercadoLibreAccessForProfile } from "../../../config/mercadolibre.js";
|
|
4
|
+
import {
|
|
5
|
+
createMercadoLibreClient,
|
|
6
|
+
formatMercadoLibreError,
|
|
7
|
+
getMercadoLibreApiContext,
|
|
8
|
+
getMercadoLibreUsersMe,
|
|
9
|
+
isRetryableMercadoLibreError,
|
|
10
|
+
MercadoLibreApiError,
|
|
11
|
+
buildMercadoLibrePaginationMetadata,
|
|
12
|
+
normalizeMercadoLibrePaging,
|
|
13
|
+
parseMercadoLibreMultiget,
|
|
14
|
+
runMercadoLibreBatch,
|
|
15
|
+
sanitizeMercadoLibreText
|
|
16
|
+
} from "../mercadolibre-api.js";
|
|
17
|
+
vi.mock("../../../config/mercadolibre.js", () => ({
|
|
18
|
+
getMercadoLibreAccessForProfile: vi.fn()
|
|
19
|
+
}));
|
|
20
|
+
describe("mercadolibre-api foundations", () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
it("redacts sensitive tokens from MercadoLibre errors", () => {
|
|
29
|
+
const sanitized = sanitizeMercadoLibreText(
|
|
30
|
+
"Bearer abc.def access_token=secret refresh_token=refresh client_secret=topsecret code=oauth"
|
|
31
|
+
);
|
|
32
|
+
expect(sanitized).toContain("Bearer [redacted]");
|
|
33
|
+
expect(sanitized).toContain("access_token=[redacted]");
|
|
34
|
+
expect(sanitized).toContain("refresh_token=[redacted]");
|
|
35
|
+
expect(sanitized).toContain("client_secret=[redacted]");
|
|
36
|
+
expect(sanitized).toContain("code=[redacted]");
|
|
37
|
+
});
|
|
38
|
+
it("normalizes paging values and builds continuation metadata", () => {
|
|
39
|
+
const paging = normalizeMercadoLibrePaging({ total: 25, limit: "10", offset: -4 });
|
|
40
|
+
const metadata = buildMercadoLibrePaginationMetadata({
|
|
41
|
+
total: paging.total,
|
|
42
|
+
limit: paging.limit,
|
|
43
|
+
offset: paging.offset,
|
|
44
|
+
returned: 10,
|
|
45
|
+
nextField: "offset"
|
|
46
|
+
});
|
|
47
|
+
expect(paging).toEqual({ total: 25, limit: 10, offset: 0 });
|
|
48
|
+
expect(metadata).toEqual({
|
|
49
|
+
total: 25,
|
|
50
|
+
limit: 10,
|
|
51
|
+
offset: 0,
|
|
52
|
+
returned: 10,
|
|
53
|
+
has_more: true,
|
|
54
|
+
continuation: "Reenviar la misma tool con offset=10 para continuar."
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it("normalizes invalid paging values and reports finished pagination when no more pages exist", () => {
|
|
58
|
+
const paging = normalizeMercadoLibrePaging({ total: "oops", limit: void 0, offset: Number.NaN });
|
|
59
|
+
const metadata = buildMercadoLibrePaginationMetadata({
|
|
60
|
+
total: 2,
|
|
61
|
+
limit: 0,
|
|
62
|
+
offset: 0,
|
|
63
|
+
returned: 2,
|
|
64
|
+
nextField: "scroll_id"
|
|
65
|
+
});
|
|
66
|
+
expect(paging).toEqual({ total: 0, limit: 0, offset: 0 });
|
|
67
|
+
expect(metadata).toEqual({
|
|
68
|
+
total: 2,
|
|
69
|
+
limit: 2,
|
|
70
|
+
offset: 0,
|
|
71
|
+
returned: 2,
|
|
72
|
+
has_more: false,
|
|
73
|
+
continuation: "No more pages for this query."
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it("filters multiget responses to successful bodies only", async () => {
|
|
77
|
+
const result = await parseMercadoLibreMultiget({
|
|
78
|
+
data: [
|
|
79
|
+
{ code: 200, body: { id: "MLA1" } },
|
|
80
|
+
{ code: 404, body: { id: "MLA2" } },
|
|
81
|
+
{ code: 204 },
|
|
82
|
+
{ code: 201, body: { id: "MLA3" } }
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
expect(result).toEqual([{ id: "MLA1" }, { id: "MLA3" }]);
|
|
86
|
+
});
|
|
87
|
+
it("returns an empty multiget payload when no successful bodies are present", async () => {
|
|
88
|
+
const result = await parseMercadoLibreMultiget({
|
|
89
|
+
data: [{ code: 404, body: { id: "missing" } }, { code: 204 }, { code: 200 }]
|
|
90
|
+
});
|
|
91
|
+
expect(result).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
it("retries retryable batch failures and keeps ordering stable", async () => {
|
|
94
|
+
vi.useFakeTimers();
|
|
95
|
+
vi.spyOn(Math, "random").mockReturnValue(0);
|
|
96
|
+
const fetcher = vi.fn().mockImplementationOnce(async () => {
|
|
97
|
+
throw new MercadoLibreApiError("rate limited", 429);
|
|
98
|
+
}).mockResolvedValueOnce({ id: "one", recovered: true }).mockResolvedValueOnce({ id: "two" });
|
|
99
|
+
const promise = runMercadoLibreBatch(["one", "two"], fetcher, {
|
|
100
|
+
maxConcurrency: 1,
|
|
101
|
+
maxRetries: 1,
|
|
102
|
+
baseRetryDelayMs: 50
|
|
103
|
+
});
|
|
104
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
105
|
+
await expect(promise).resolves.toEqual({
|
|
106
|
+
successful: [
|
|
107
|
+
{ id: "one", document: { id: "one", recovered: true } },
|
|
108
|
+
{ id: "two", document: { id: "two" } }
|
|
109
|
+
],
|
|
110
|
+
failed: []
|
|
111
|
+
});
|
|
112
|
+
expect(fetcher).toHaveBeenCalledTimes(3);
|
|
113
|
+
});
|
|
114
|
+
it("honors retry-after headers for retryable failures", async () => {
|
|
115
|
+
vi.useFakeTimers();
|
|
116
|
+
const fetcher = vi.fn().mockImplementationOnce(async () => {
|
|
117
|
+
throw new MercadoLibreApiError("wait", 429, void 0, 120);
|
|
118
|
+
}).mockResolvedValueOnce({ id: "one" });
|
|
119
|
+
const promise = runMercadoLibreBatch(["one"], fetcher, {
|
|
120
|
+
maxRetries: 1,
|
|
121
|
+
baseRetryDelayMs: 50
|
|
122
|
+
});
|
|
123
|
+
await vi.advanceTimersByTimeAsync(119);
|
|
124
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
125
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
126
|
+
await expect(promise).resolves.toEqual({
|
|
127
|
+
successful: [{ id: "one", document: { id: "one" } }],
|
|
128
|
+
failed: []
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
it("classifies non-retryable batch failures with a safe message", async () => {
|
|
132
|
+
const result = await runMercadoLibreBatch(
|
|
133
|
+
["broken"],
|
|
134
|
+
async () => {
|
|
135
|
+
throw new Error("refresh_token=secret exploded");
|
|
136
|
+
},
|
|
137
|
+
{ maxRetries: 2 }
|
|
138
|
+
);
|
|
139
|
+
expect(result).toEqual({
|
|
140
|
+
successful: [],
|
|
141
|
+
failed: [
|
|
142
|
+
{
|
|
143
|
+
id: "broken",
|
|
144
|
+
message: "refresh_token=[redacted] exploded",
|
|
145
|
+
statusCode: void 0,
|
|
146
|
+
attempts: 1,
|
|
147
|
+
retryable: false
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
it("treats unknown rejected batch reasons as a single failed attempt", async () => {
|
|
153
|
+
const fetcher = vi.fn(async () => {
|
|
154
|
+
throw "Bearer abc123";
|
|
155
|
+
});
|
|
156
|
+
const result = await runMercadoLibreBatch(["broken"], fetcher, { maxRetries: 0 });
|
|
157
|
+
expect(result).toEqual({
|
|
158
|
+
successful: [],
|
|
159
|
+
failed: [
|
|
160
|
+
{
|
|
161
|
+
id: "broken",
|
|
162
|
+
message: "Failed to fetch MercadoLibre resource broken",
|
|
163
|
+
statusCode: void 0,
|
|
164
|
+
attempts: 1,
|
|
165
|
+
retryable: false
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
it("returns an empty batch result when no ids are provided", async () => {
|
|
171
|
+
const fetcher = vi.fn();
|
|
172
|
+
await expect(runMercadoLibreBatch([], fetcher)).resolves.toEqual({
|
|
173
|
+
successful: [],
|
|
174
|
+
failed: []
|
|
175
|
+
});
|
|
176
|
+
expect(fetcher).not.toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
it("formats axios errors with sanitized response details", () => {
|
|
179
|
+
const error = {
|
|
180
|
+
isAxiosError: true,
|
|
181
|
+
message: "Request failed with status code 400",
|
|
182
|
+
response: {
|
|
183
|
+
status: 400,
|
|
184
|
+
data: {
|
|
185
|
+
error: "bad_request",
|
|
186
|
+
cause: [{ message: "refresh_token=top-secret" }]
|
|
187
|
+
},
|
|
188
|
+
headers: {}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
expect(formatMercadoLibreError(error)).toBe(
|
|
192
|
+
"MercadoLibre API request failed (400): refresh_token=[redacted]"
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
it("creates a client with auth headers and converts axios failures into MercadoLibreApiError", async () => {
|
|
196
|
+
const responseUse = vi.fn();
|
|
197
|
+
const client = {
|
|
198
|
+
interceptors: { response: { use: responseUse } }
|
|
199
|
+
};
|
|
200
|
+
vi.spyOn(axios, "create").mockReturnValue(client);
|
|
201
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => void 0);
|
|
202
|
+
createMercadoLibreClient("access-token");
|
|
203
|
+
expect(axios.create).toHaveBeenCalledWith({
|
|
204
|
+
baseURL: "https://api.mercadolibre.com",
|
|
205
|
+
timeout: 3e4,
|
|
206
|
+
headers: {
|
|
207
|
+
Accept: "application/json",
|
|
208
|
+
"Content-Type": "application/json",
|
|
209
|
+
Authorization: "Bearer access-token"
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
const rejectHandler = responseUse.mock.calls[0]?.[1];
|
|
213
|
+
const retryAt = new Date(Date.now() + 5e3).toUTCString();
|
|
214
|
+
await expect(
|
|
215
|
+
rejectHandler({
|
|
216
|
+
isAxiosError: true,
|
|
217
|
+
message: "boom",
|
|
218
|
+
config: { method: "get", url: "/orders/search" },
|
|
219
|
+
response: {
|
|
220
|
+
status: 429,
|
|
221
|
+
headers: { "retry-after": retryAt },
|
|
222
|
+
data: { error: "rate_limit", message: "access_token=secret" }
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
).rejects.toMatchObject({
|
|
226
|
+
name: "MercadoLibreApiError",
|
|
227
|
+
status: 429,
|
|
228
|
+
code: "rate_limit",
|
|
229
|
+
message: "MercadoLibre API request failed (429): access_token=[redacted]",
|
|
230
|
+
retryAfterMs: expect.any(Number)
|
|
231
|
+
});
|
|
232
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
233
|
+
"[MERCADOLIBRE API ERROR] GET /orders/search -> 429"
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
it("builds api context from the persisted profile access and exposes /users/me", async () => {
|
|
237
|
+
const getMock = vi.fn().mockResolvedValue({ data: { id: 123, nickname: "seller" } });
|
|
238
|
+
const client = {
|
|
239
|
+
get: getMock,
|
|
240
|
+
interceptors: { response: { use: vi.fn() } }
|
|
241
|
+
};
|
|
242
|
+
vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({
|
|
243
|
+
sellerId: "seller-1",
|
|
244
|
+
accessToken: "stored-token",
|
|
245
|
+
refreshToken: "refresh"
|
|
246
|
+
});
|
|
247
|
+
vi.spyOn(axios, "create").mockReturnValue(client);
|
|
248
|
+
const context = await getMercadoLibreApiContext("profile-1");
|
|
249
|
+
const me = await getMercadoLibreUsersMe("profile-1");
|
|
250
|
+
expect(context).toMatchObject({
|
|
251
|
+
sellerId: "seller-1",
|
|
252
|
+
accessToken: "stored-token",
|
|
253
|
+
refreshToken: "refresh",
|
|
254
|
+
api: client
|
|
255
|
+
});
|
|
256
|
+
expect(getMock).toHaveBeenCalledWith("/users/me");
|
|
257
|
+
expect(me).toEqual({ id: 123, nickname: "seller" });
|
|
258
|
+
});
|
|
259
|
+
it("detects retryable MercadoLibre errors including missing status codes", () => {
|
|
260
|
+
expect(isRetryableMercadoLibreError(new MercadoLibreApiError("network"))).toBe(true);
|
|
261
|
+
expect(isRetryableMercadoLibreError(new MercadoLibreApiError("forbidden", 403))).toBe(false);
|
|
262
|
+
expect(isRetryableMercadoLibreError(new Error("plain"))).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
//# sourceMappingURL=mercadolibre-api.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/services/mercadolibre/__tests__/mercadolibre-api.test.ts"],
|
|
4
|
+
"sourcesContent": ["import axios, { AxiosError } from \"axios\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { getMercadoLibreAccessForProfile } from \"../../../config/mercadolibre.js\";\nimport {\n createMercadoLibreClient,\n formatMercadoLibreError,\n getMercadoLibreApiContext,\n getMercadoLibreUsersMe,\n isRetryableMercadoLibreError,\n MercadoLibreApiError,\n buildMercadoLibrePaginationMetadata,\n normalizeMercadoLibrePaging,\n parseMercadoLibreMultiget,\n runMercadoLibreBatch,\n sanitizeMercadoLibreText,\n} from \"../mercadolibre-api.js\";\n\nvi.mock(\"../../../config/mercadolibre.js\", () => ({\n getMercadoLibreAccessForProfile: vi.fn(),\n}));\n\ndescribe(\"mercadolibre-api foundations\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n vi.restoreAllMocks();\n });\n\n it(\"redacts sensitive tokens from MercadoLibre errors\", () => {\n const sanitized = sanitizeMercadoLibreText(\n \"Bearer abc.def access_token=secret refresh_token=refresh client_secret=topsecret code=oauth\"\n );\n\n expect(sanitized).toContain(\"Bearer [redacted]\");\n expect(sanitized).toContain(\"access_token=[redacted]\");\n expect(sanitized).toContain(\"refresh_token=[redacted]\");\n expect(sanitized).toContain(\"client_secret=[redacted]\");\n expect(sanitized).toContain(\"code=[redacted]\");\n });\n\n it(\"normalizes paging values and builds continuation metadata\", () => {\n const paging = normalizeMercadoLibrePaging({ total: 25, limit: \"10\", offset: -4 });\n const metadata = buildMercadoLibrePaginationMetadata({\n total: paging.total,\n limit: paging.limit,\n offset: paging.offset,\n returned: 10,\n nextField: \"offset\",\n });\n\n expect(paging).toEqual({ total: 25, limit: 10, offset: 0 });\n expect(metadata).toEqual({\n total: 25,\n limit: 10,\n offset: 0,\n returned: 10,\n has_more: true,\n continuation: \"Reenviar la misma tool con offset=10 para continuar.\",\n });\n });\n\n it(\"normalizes invalid paging values and reports finished pagination when no more pages exist\", () => {\n const paging = normalizeMercadoLibrePaging({ total: \"oops\", limit: undefined, offset: Number.NaN });\n const metadata = buildMercadoLibrePaginationMetadata({\n total: 2,\n limit: 0,\n offset: 0,\n returned: 2,\n nextField: \"scroll_id\",\n });\n\n expect(paging).toEqual({ total: 0, limit: 0, offset: 0 });\n expect(metadata).toEqual({\n total: 2,\n limit: 2,\n offset: 0,\n returned: 2,\n has_more: false,\n continuation: \"No more pages for this query.\",\n });\n });\n\n it(\"filters multiget responses to successful bodies only\", async () => {\n const result = await parseMercadoLibreMultiget({\n data: [\n { code: 200, body: { id: \"MLA1\" } },\n { code: 404, body: { id: \"MLA2\" } },\n { code: 204 },\n { code: 201, body: { id: \"MLA3\" } },\n ],\n } as never);\n\n expect(result).toEqual([{ id: \"MLA1\" }, { id: \"MLA3\" }]);\n });\n\n it(\"returns an empty multiget payload when no successful bodies are present\", async () => {\n const result = await parseMercadoLibreMultiget({\n data: [{ code: 404, body: { id: \"missing\" } }, { code: 204 }, { code: 200 }],\n } as never);\n\n expect(result).toEqual([]);\n });\n\n it(\"retries retryable batch failures and keeps ordering stable\", async () => {\n vi.useFakeTimers();\n vi.spyOn(Math, \"random\").mockReturnValue(0);\n\n const fetcher = vi\n .fn<(id: string) => Promise<{ id: string; recovered?: boolean }>>()\n .mockImplementationOnce(async () => {\n throw new MercadoLibreApiError(\"rate limited\", 429);\n })\n .mockResolvedValueOnce({ id: \"one\", recovered: true })\n .mockResolvedValueOnce({ id: \"two\" });\n\n const promise = runMercadoLibreBatch([\"one\", \"two\"], fetcher, {\n maxConcurrency: 1,\n maxRetries: 1,\n baseRetryDelayMs: 50,\n });\n\n await vi.advanceTimersByTimeAsync(50);\n\n await expect(promise).resolves.toEqual({\n successful: [\n { id: \"one\", document: { id: \"one\", recovered: true } },\n { id: \"two\", document: { id: \"two\" } },\n ],\n failed: [],\n });\n expect(fetcher).toHaveBeenCalledTimes(3);\n });\n\n it(\"honors retry-after headers for retryable failures\", async () => {\n vi.useFakeTimers();\n\n const fetcher = vi\n .fn<(id: string) => Promise<{ id: string }>>()\n .mockImplementationOnce(async () => {\n throw new MercadoLibreApiError(\"wait\", 429, undefined, 120);\n })\n .mockResolvedValueOnce({ id: \"one\" });\n\n const promise = runMercadoLibreBatch([\"one\"], fetcher, {\n maxRetries: 1,\n baseRetryDelayMs: 50,\n });\n\n await vi.advanceTimersByTimeAsync(119);\n expect(fetcher).toHaveBeenCalledTimes(1);\n\n await vi.advanceTimersByTimeAsync(1);\n\n await expect(promise).resolves.toEqual({\n successful: [{ id: \"one\", document: { id: \"one\" } }],\n failed: [],\n });\n });\n\n it(\"classifies non-retryable batch failures with a safe message\", async () => {\n const result = await runMercadoLibreBatch(\n [\"broken\"],\n async () => {\n throw new Error(\"refresh_token=secret exploded\");\n },\n { maxRetries: 2 }\n );\n\n expect(result).toEqual({\n successful: [],\n failed: [\n {\n id: \"broken\",\n message: \"refresh_token=[redacted] exploded\",\n statusCode: undefined,\n attempts: 1,\n retryable: false,\n },\n ],\n });\n });\n\n it(\"treats unknown rejected batch reasons as a single failed attempt\", async () => {\n const fetcher = vi.fn(async () => {\n throw \"Bearer abc123\";\n });\n\n const result = await runMercadoLibreBatch([\"broken\"], fetcher, { maxRetries: 0 });\n\n expect(result).toEqual({\n successful: [],\n failed: [\n {\n id: \"broken\",\n message: \"Failed to fetch MercadoLibre resource broken\",\n statusCode: undefined,\n attempts: 1,\n retryable: false,\n },\n ],\n });\n });\n\n it(\"returns an empty batch result when no ids are provided\", async () => {\n const fetcher = vi.fn();\n\n await expect(runMercadoLibreBatch([], fetcher)).resolves.toEqual({\n successful: [],\n failed: [],\n });\n expect(fetcher).not.toHaveBeenCalled();\n });\n\n it(\"formats axios errors with sanitized response details\", () => {\n const error = {\n isAxiosError: true,\n message: \"Request failed with status code 400\",\n response: {\n status: 400,\n data: {\n error: \"bad_request\",\n cause: [{ message: \"refresh_token=top-secret\" }],\n },\n headers: {},\n },\n } as AxiosError;\n\n expect(formatMercadoLibreError(error)).toBe(\n \"MercadoLibre API request failed (400): refresh_token=[redacted]\"\n );\n });\n\n it(\"creates a client with auth headers and converts axios failures into MercadoLibreApiError\", async () => {\n const responseUse = vi.fn();\n const client = {\n interceptors: { response: { use: responseUse } },\n };\n vi.spyOn(axios, \"create\").mockReturnValue(client as never);\n const consoleSpy = vi.spyOn(console, \"error\").mockImplementation(() => undefined);\n\n createMercadoLibreClient(\"access-token\");\n\n expect(axios.create).toHaveBeenCalledWith({\n baseURL: \"https://api.mercadolibre.com\",\n timeout: 30000,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n Authorization: \"Bearer access-token\",\n },\n });\n\n const rejectHandler = responseUse.mock.calls[0]?.[1] as (error: AxiosError) => Promise<never>;\n const retryAt = new Date(Date.now() + 5000).toUTCString();\n\n await expect(\n rejectHandler({\n isAxiosError: true,\n message: \"boom\",\n config: { method: \"get\", url: \"/orders/search\" },\n response: {\n status: 429,\n headers: { \"retry-after\": retryAt },\n data: { error: \"rate_limit\", message: \"access_token=secret\" },\n },\n } as unknown as AxiosError)\n ).rejects.toMatchObject({\n name: \"MercadoLibreApiError\",\n status: 429,\n code: \"rate_limit\",\n message: \"MercadoLibre API request failed (429): access_token=[redacted]\",\n retryAfterMs: expect.any(Number),\n });\n\n expect(consoleSpy).toHaveBeenCalledWith(\n \"[MERCADOLIBRE API ERROR] GET /orders/search -> 429\"\n );\n });\n\n it(\"builds api context from the persisted profile access and exposes /users/me\", async () => {\n const getMock = vi.fn().mockResolvedValue({ data: { id: 123, nickname: \"seller\" } });\n const client = {\n get: getMock,\n interceptors: { response: { use: vi.fn() } },\n };\n\n vi.mocked(getMercadoLibreAccessForProfile).mockResolvedValue({\n sellerId: \"seller-1\",\n accessToken: \"stored-token\",\n refreshToken: \"refresh\",\n } as never);\n vi.spyOn(axios, \"create\").mockReturnValue(client as never);\n\n const context = await getMercadoLibreApiContext(\"profile-1\");\n const me = await getMercadoLibreUsersMe(\"profile-1\");\n\n expect(context).toMatchObject({\n sellerId: \"seller-1\",\n accessToken: \"stored-token\",\n refreshToken: \"refresh\",\n api: client,\n });\n expect(getMock).toHaveBeenCalledWith(\"/users/me\");\n expect(me).toEqual({ id: 123, nickname: \"seller\" });\n });\n\n it(\"detects retryable MercadoLibre errors including missing status codes\", () => {\n expect(isRetryableMercadoLibreError(new MercadoLibreApiError(\"network\"))).toBe(true);\n expect(isRetryableMercadoLibreError(new MercadoLibreApiError(\"forbidden\", 403))).toBe(false);\n expect(isRetryableMercadoLibreError(new Error(\"plain\"))).toBe(false);\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|