@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.
Files changed (237) hide show
  1. package/README.md +4 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/index.js +398 -8
  4. package/dist/index.js.map +2 -2
  5. package/dist/mcp-use.json +2 -2
  6. package/dist/src/config/__tests__/meta.test.js +35 -0
  7. package/dist/src/config/__tests__/meta.test.js.map +7 -0
  8. package/dist/src/config/meta-store.js +109 -0
  9. package/dist/src/config/meta-store.js.map +7 -0
  10. package/dist/src/config/meta.js +0 -2
  11. package/dist/src/config/meta.js.map +2 -2
  12. package/dist/src/meta/__tests__/meta-utils.test.js +155 -0
  13. package/dist/src/meta/__tests__/meta-utils.test.js.map +7 -0
  14. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js +265 -0
  15. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js.map +7 -0
  16. package/dist/src/services/mercadolibre/__tests__/mercadolibre-items.test.js +311 -0
  17. package/dist/src/services/mercadolibre/__tests__/mercadolibre-items.test.js.map +7 -0
  18. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js +220 -0
  19. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js.map +7 -0
  20. package/dist/src/services/meta/__tests__/meta-ads.test.js +126 -0
  21. package/dist/src/services/meta/__tests__/meta-ads.test.js.map +7 -0
  22. package/dist/src/services/meta/__tests__/meta-api.test.js +70 -0
  23. package/dist/src/services/meta/__tests__/meta-api.test.js.map +7 -0
  24. package/dist/src/services/meta/meta-ads.js +94 -40
  25. package/dist/src/services/meta/meta-ads.js.map +2 -2
  26. package/dist/src/services/meta/meta-api.js +8 -6
  27. package/dist/src/services/meta/meta-api.js.map +2 -2
  28. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js +165 -0
  29. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js.map +7 -0
  30. package/dist/src/services/vtex/__tests__/vtex-catalog-write-eans.test.js +92 -0
  31. package/dist/src/services/vtex/__tests__/vtex-catalog-write-eans.test.js.map +7 -0
  32. package/dist/src/services/vtex/__tests__/vtex-catalog-write-products.test.js +41 -0
  33. package/dist/src/services/vtex/__tests__/vtex-catalog-write-products.test.js.map +7 -0
  34. package/dist/src/services/vtex/__tests__/vtex-catalog.test.js +80 -0
  35. package/dist/src/services/vtex/__tests__/vtex-catalog.test.js.map +7 -0
  36. package/dist/src/services/vtex/__tests__/vtex-orders-write-http.test.js +85 -0
  37. package/dist/src/services/vtex/__tests__/vtex-orders-write-http.test.js.map +7 -0
  38. package/dist/src/services/vtex/__tests__/vtex-orders-write-state-validation.test.js +251 -0
  39. package/dist/src/services/vtex/__tests__/vtex-orders-write-state-validation.test.js.map +7 -0
  40. package/dist/src/services/vtex/__tests__/vtex-write.test.js +111 -0
  41. package/dist/src/services/vtex/__tests__/vtex-write.test.js.map +7 -0
  42. package/dist/src/services/vtex/vtex-catalog-write.js +194 -1
  43. package/dist/src/services/vtex/vtex-catalog-write.js.map +2 -2
  44. package/dist/src/services/vtex/vtex-catalog.js +118 -1
  45. package/dist/src/services/vtex/vtex-catalog.js.map +2 -2
  46. package/dist/src/services/vtex/vtex-pricing-write.js +5 -0
  47. package/dist/src/services/vtex/vtex-pricing-write.js.map +2 -2
  48. package/dist/src/services/vtex/vtex-write.js +67 -1
  49. package/dist/src/services/vtex/vtex-write.js.map +2 -2
  50. package/dist/src/tools/config/list-profiles.js +37 -6
  51. package/dist/src/tools/config/list-profiles.js.map +2 -2
  52. package/dist/src/tools/index.js.map +2 -2
  53. package/dist/src/tools/mercadolibre/__tests__/helpers.test.js +37 -0
  54. package/dist/src/tools/mercadolibre/__tests__/helpers.test.js.map +7 -0
  55. package/dist/src/tools/mercadolibre/__tests__/profile-resolution.test.js +30 -0
  56. package/dist/src/tools/mercadolibre/__tests__/profile-resolution.test.js.map +7 -0
  57. package/dist/src/tools/mercadolibre/profile-resolution.js +4 -50
  58. package/dist/src/tools/mercadolibre/profile-resolution.js.map +2 -2
  59. package/dist/src/tools/meta/__tests__/pagination.test.js +133 -0
  60. package/dist/src/tools/meta/__tests__/pagination.test.js.map +7 -0
  61. package/dist/src/tools/meta/__tests__/profile-access.test.js +262 -0
  62. package/dist/src/tools/meta/__tests__/profile-access.test.js.map +7 -0
  63. package/dist/src/tools/meta/__tests__/read-tools.test.js +722 -0
  64. package/dist/src/tools/meta/__tests__/read-tools.test.js.map +7 -0
  65. package/dist/src/tools/meta/__tests__/schemas.test.js +103 -0
  66. package/dist/src/tools/meta/__tests__/schemas.test.js.map +7 -0
  67. package/dist/src/tools/meta/account-overview.js +37 -19
  68. package/dist/src/tools/meta/account-overview.js.map +2 -2
  69. package/dist/src/tools/meta/ad-account-info.js +31 -6
  70. package/dist/src/tools/meta/ad-account-info.js.map +2 -2
  71. package/dist/src/tools/meta/ads-performance.js +35 -21
  72. package/dist/src/tools/meta/ads-performance.js.map +2 -2
  73. package/dist/src/tools/meta/campaign-performance.js +35 -18
  74. package/dist/src/tools/meta/campaign-performance.js.map +2 -2
  75. package/dist/src/tools/meta/list-accessible-ad-accounts.js +39 -10
  76. package/dist/src/tools/meta/list-accessible-ad-accounts.js.map +2 -2
  77. package/dist/src/tools/meta/list-accessible-businesses.js +37 -13
  78. package/dist/src/tools/meta/list-accessible-businesses.js.map +2 -2
  79. package/dist/src/tools/meta/placement-mix.js +37 -22
  80. package/dist/src/tools/meta/placement-mix.js.map +2 -2
  81. package/dist/src/tools/meta/profile-access.js +215 -0
  82. package/dist/src/tools/meta/profile-access.js.map +7 -0
  83. package/dist/src/tools/meta/schema-helpers.js +19 -0
  84. package/dist/src/tools/meta/schema-helpers.js.map +7 -0
  85. package/dist/src/tools/meta/time-series.js +40 -23
  86. package/dist/src/tools/meta/time-series.js.map +2 -2
  87. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js +233 -0
  88. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js.map +7 -0
  89. package/dist/src/tools/vtex/__tests__/catalog-admin-categories.test.js +649 -0
  90. package/dist/src/tools/vtex/__tests__/catalog-admin-categories.test.js.map +7 -0
  91. package/dist/src/tools/vtex/__tests__/catalog-admin-product-specifications.test.js +339 -0
  92. package/dist/src/tools/vtex/__tests__/catalog-admin-product-specifications.test.js.map +7 -0
  93. package/dist/src/tools/vtex/__tests__/catalog-admin-products-skus.test.js +235 -0
  94. package/dist/src/tools/vtex/__tests__/catalog-admin-products-skus.test.js.map +7 -0
  95. package/dist/src/tools/vtex/__tests__/catalog-navigation-reads.test.js +372 -0
  96. package/dist/src/tools/vtex/__tests__/catalog-navigation-reads.test.js.map +7 -0
  97. package/dist/src/tools/vtex/__tests__/catalog-navigation-surface.test.js +57 -0
  98. package/dist/src/tools/vtex/__tests__/catalog-navigation-surface.test.js.map +7 -0
  99. package/dist/src/tools/vtex/__tests__/write-helpers.test.js +97 -0
  100. package/dist/src/tools/vtex/__tests__/write-helpers.test.js.map +7 -0
  101. package/dist/src/tools/vtex/activate-sku.js +1 -48
  102. package/dist/src/tools/vtex/activate-sku.js.map +2 -2
  103. package/dist/src/tools/vtex/associate-specification.js +3 -54
  104. package/dist/src/tools/vtex/associate-specification.js.map +2 -2
  105. package/dist/src/tools/vtex/attach-catalog-image.js +3 -57
  106. package/dist/src/tools/vtex/attach-catalog-image.js.map +2 -2
  107. package/dist/src/tools/vtex/catalog-admin-batch.js +298 -0
  108. package/dist/src/tools/vtex/catalog-admin-batch.js.map +7 -0
  109. package/dist/src/tools/vtex/catalog-admin-categories.js +542 -0
  110. package/dist/src/tools/vtex/catalog-admin-categories.js.map +7 -0
  111. package/dist/src/tools/vtex/catalog-admin-product-specifications.js +275 -0
  112. package/dist/src/tools/vtex/catalog-admin-product-specifications.js.map +7 -0
  113. package/dist/src/tools/vtex/catalog-admin-products-skus.js +475 -0
  114. package/dist/src/tools/vtex/catalog-admin-products-skus.js.map +7 -0
  115. package/dist/src/tools/vtex/catalog-navigation-reads.js +430 -0
  116. package/dist/src/tools/vtex/catalog-navigation-reads.js.map +7 -0
  117. package/dist/src/tools/vtex/create-brand.js +1 -64
  118. package/dist/src/tools/vtex/create-brand.js.map +2 -2
  119. package/dist/src/tools/vtex/create-category.js +1 -76
  120. package/dist/src/tools/vtex/create-category.js.map +2 -2
  121. package/dist/src/tools/vtex/create-product-with-sku.js +3 -114
  122. package/dist/src/tools/vtex/create-product-with-sku.js.map +2 -2
  123. package/dist/src/tools/vtex/create-specification-value.js +3 -47
  124. package/dist/src/tools/vtex/create-specification-value.js.map +2 -2
  125. package/dist/src/tools/vtex/create-specification.js +1 -80
  126. package/dist/src/tools/vtex/create-specification.js.map +2 -2
  127. package/dist/src/tools/vtex/deactivate-sku.js +1 -48
  128. package/dist/src/tools/vtex/deactivate-sku.js.map +2 -2
  129. package/dist/src/tools/vtex/delete-all-product-specifications.js +9 -0
  130. package/dist/src/tools/vtex/delete-all-product-specifications.js.map +7 -0
  131. package/dist/src/tools/vtex/delete-all-sku-specifications.js +9 -0
  132. package/dist/src/tools/vtex/delete-all-sku-specifications.js.map +7 -0
  133. package/dist/src/tools/vtex/delete-brand.js +9 -0
  134. package/dist/src/tools/vtex/delete-brand.js.map +7 -0
  135. package/dist/src/tools/vtex/delete-catalog-image.js +9 -0
  136. package/dist/src/tools/vtex/delete-catalog-image.js.map +7 -0
  137. package/dist/src/tools/vtex/delete-product-specification.js +9 -0
  138. package/dist/src/tools/vtex/delete-product-specification.js.map +7 -0
  139. package/dist/src/tools/vtex/delete-sku-price.js +55 -0
  140. package/dist/src/tools/vtex/delete-sku-price.js.map +7 -0
  141. package/dist/src/tools/vtex/delete-sku-specification.js +9 -0
  142. package/dist/src/tools/vtex/delete-sku-specification.js.map +7 -0
  143. package/dist/src/tools/vtex/get-brand.js +6 -0
  144. package/dist/src/tools/vtex/get-brand.js.map +7 -0
  145. package/dist/src/tools/vtex/get-category-tree.js +6 -0
  146. package/dist/src/tools/vtex/get-category-tree.js.map +7 -0
  147. package/dist/src/tools/vtex/get-category.js +6 -0
  148. package/dist/src/tools/vtex/get-category.js.map +7 -0
  149. package/dist/src/tools/vtex/get-product-specifications.js +9 -0
  150. package/dist/src/tools/vtex/get-product-specifications.js.map +7 -0
  151. package/dist/src/tools/vtex/get-product.js +6 -0
  152. package/dist/src/tools/vtex/get-product.js.map +7 -0
  153. package/dist/src/tools/vtex/get-sku.js +6 -0
  154. package/dist/src/tools/vtex/get-sku.js.map +7 -0
  155. package/dist/src/tools/vtex/index.js +23 -1
  156. package/dist/src/tools/vtex/index.js.map +2 -2
  157. package/dist/src/tools/vtex/list-brands.js +6 -0
  158. package/dist/src/tools/vtex/list-brands.js.map +7 -0
  159. package/dist/src/tools/vtex/list-categories.js +6 -0
  160. package/dist/src/tools/vtex/list-categories.js.map +7 -0
  161. package/dist/src/tools/vtex/list-products-by-category.js +6 -0
  162. package/dist/src/tools/vtex/list-products-by-category.js.map +7 -0
  163. package/dist/src/tools/vtex/list-products.js +6 -0
  164. package/dist/src/tools/vtex/list-products.js.map +7 -0
  165. package/dist/src/tools/vtex/list-skus-by-product.js +6 -0
  166. package/dist/src/tools/vtex/list-skus-by-product.js.map +7 -0
  167. package/dist/src/tools/vtex/list-specification-groups.js +9 -0
  168. package/dist/src/tools/vtex/list-specification-groups.js.map +7 -0
  169. package/dist/src/tools/vtex/move-category.js +6 -0
  170. package/dist/src/tools/vtex/move-category.js.map +7 -0
  171. package/dist/src/tools/vtex/profile-resolution.js +4 -51
  172. package/dist/src/tools/vtex/profile-resolution.js.map +2 -2
  173. package/dist/src/tools/vtex/update-brand.js +6 -0
  174. package/dist/src/tools/vtex/update-brand.js.map +7 -0
  175. package/dist/src/tools/vtex/update-category.js +6 -0
  176. package/dist/src/tools/vtex/update-category.js.map +7 -0
  177. package/dist/src/tools/vtex/update-product-basic-fields.js +3 -65
  178. package/dist/src/tools/vtex/update-product-basic-fields.js.map +2 -2
  179. package/dist/src/tools/vtex/update-sku-basic-fields.js +1 -87
  180. package/dist/src/tools/vtex/update-sku-basic-fields.js.map +2 -2
  181. package/dist/src/tools/vtex/update-sku-price.js +21 -1
  182. package/dist/src/tools/vtex/update-sku-price.js.map +2 -2
  183. package/dist/src/tools/vtex/write-helpers.js +104 -14
  184. package/dist/src/tools/vtex/write-helpers.js.map +2 -2
  185. package/dist/src/utils/provider-profile-selection.js +117 -0
  186. package/dist/src/utils/provider-profile-selection.js.map +7 -0
  187. package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js +678 -0
  188. package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js.map +7 -0
  189. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js +564 -0
  190. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js.map +7 -0
  191. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js +387 -0
  192. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js.map +7 -0
  193. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js +368 -0
  194. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js.map +7 -0
  195. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js +626 -0
  196. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js.map +7 -0
  197. package/dist/tests/meli/mercadolibre-tool-handlers-write-batch8.test.js +480 -0
  198. package/dist/tests/meli/mercadolibre-tool-handlers-write-batch8.test.js.map +7 -0
  199. package/dist/tests/setup.js +2 -0
  200. package/dist/tests/setup.js.map +7 -0
  201. package/dist/tests/smoke/test-harness.test.js +7 -0
  202. package/dist/tests/smoke/test-harness.test.js.map +7 -0
  203. package/dist/tests/vtex/read-only-utils.test.js +161 -0
  204. package/dist/tests/vtex/read-only-utils.test.js.map +7 -0
  205. package/dist/tests/vtex/vtex-catalog-admin-docs.test.js +24 -0
  206. package/dist/tests/vtex/vtex-catalog-admin-docs.test.js.map +7 -0
  207. package/dist/tests/vtex/vtex-catalog-admin-surface.test.js +30 -0
  208. package/dist/tests/vtex/vtex-catalog-admin-surface.test.js.map +7 -0
  209. package/dist/tests/vtex/vtex-catalog-write-tools.test.js +712 -0
  210. package/dist/tests/vtex/vtex-catalog-write-tools.test.js.map +7 -0
  211. package/dist/tests/vtex/vtex-catalog.service.test.js +51 -0
  212. package/dist/tests/vtex/vtex-catalog.service.test.js.map +7 -0
  213. package/dist/tests/vtex/vtex-inventory-tools.test.js +201 -0
  214. package/dist/tests/vtex/vtex-inventory-tools.test.js.map +7 -0
  215. package/dist/tests/vtex/vtex-logistics.service.test.js +134 -0
  216. package/dist/tests/vtex/vtex-logistics.service.test.js.map +7 -0
  217. package/dist/tests/vtex/vtex-order-details.tool.test.js +141 -0
  218. package/dist/tests/vtex/vtex-order-details.tool.test.js.map +7 -0
  219. package/dist/tests/vtex/vtex-order-write-tools.test.js +483 -0
  220. package/dist/tests/vtex/vtex-order-write-tools.test.js.map +7 -0
  221. package/dist/tests/vtex/vtex-orders-summary.tool.test.js +185 -0
  222. package/dist/tests/vtex/vtex-orders-summary.tool.test.js.map +7 -0
  223. package/dist/tests/vtex/vtex-orders.service.test.js +120 -0
  224. package/dist/tests/vtex/vtex-orders.service.test.js.map +7 -0
  225. package/dist/tests/vtex/vtex-pricing-write-tools.test.js +202 -0
  226. package/dist/tests/vtex/vtex-pricing-write-tools.test.js.map +7 -0
  227. package/dist/tests/vtex/vtex-pricing-write.service.test.js +106 -0
  228. package/dist/tests/vtex/vtex-pricing-write.service.test.js.map +7 -0
  229. package/dist/tests/vtex/vtex-pricing.service.test.js +88 -0
  230. package/dist/tests/vtex/vtex-pricing.service.test.js.map +7 -0
  231. package/dist/tests/vtex/vtex-small-tools.test.js +190 -0
  232. package/dist/tests/vtex/vtex-small-tools.test.js.map +7 -0
  233. package/dist/tests/vtex/vtex-write-simple-tools.test.js +647 -0
  234. package/dist/tests/vtex/vtex-write-simple-tools.test.js.map +7 -0
  235. package/dist/vitest.config.js +15 -0
  236. package/dist/vitest.config.js.map +7 -0
  237. 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
+ }