@yoryoboy/bi-mcp 1.10.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 (239) 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/mercadolibre-profile-store.js +25 -0
  9. package/dist/src/config/mercadolibre-profile-store.js.map +2 -2
  10. package/dist/src/config/meta-store.js +109 -0
  11. package/dist/src/config/meta-store.js.map +7 -0
  12. package/dist/src/config/meta.js +0 -2
  13. package/dist/src/config/meta.js.map +2 -2
  14. package/dist/src/meta/__tests__/meta-utils.test.js +155 -0
  15. package/dist/src/meta/__tests__/meta-utils.test.js.map +7 -0
  16. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js +265 -0
  17. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js.map +7 -0
  18. package/dist/src/services/mercadolibre/__tests__/mercadolibre-items.test.js +311 -0
  19. package/dist/src/services/mercadolibre/__tests__/mercadolibre-items.test.js.map +7 -0
  20. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js +220 -0
  21. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js.map +7 -0
  22. package/dist/src/services/meta/__tests__/meta-ads.test.js +126 -0
  23. package/dist/src/services/meta/__tests__/meta-ads.test.js.map +7 -0
  24. package/dist/src/services/meta/__tests__/meta-api.test.js +70 -0
  25. package/dist/src/services/meta/__tests__/meta-api.test.js.map +7 -0
  26. package/dist/src/services/meta/meta-ads.js +94 -40
  27. package/dist/src/services/meta/meta-ads.js.map +2 -2
  28. package/dist/src/services/meta/meta-api.js +8 -6
  29. package/dist/src/services/meta/meta-api.js.map +2 -2
  30. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js +165 -0
  31. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js.map +7 -0
  32. package/dist/src/services/vtex/__tests__/vtex-catalog-write-eans.test.js +92 -0
  33. package/dist/src/services/vtex/__tests__/vtex-catalog-write-eans.test.js.map +7 -0
  34. package/dist/src/services/vtex/__tests__/vtex-catalog-write-products.test.js +41 -0
  35. package/dist/src/services/vtex/__tests__/vtex-catalog-write-products.test.js.map +7 -0
  36. package/dist/src/services/vtex/__tests__/vtex-catalog.test.js +80 -0
  37. package/dist/src/services/vtex/__tests__/vtex-catalog.test.js.map +7 -0
  38. package/dist/src/services/vtex/__tests__/vtex-orders-write-http.test.js +85 -0
  39. package/dist/src/services/vtex/__tests__/vtex-orders-write-http.test.js.map +7 -0
  40. package/dist/src/services/vtex/__tests__/vtex-orders-write-state-validation.test.js +251 -0
  41. package/dist/src/services/vtex/__tests__/vtex-orders-write-state-validation.test.js.map +7 -0
  42. package/dist/src/services/vtex/__tests__/vtex-write.test.js +111 -0
  43. package/dist/src/services/vtex/__tests__/vtex-write.test.js.map +7 -0
  44. package/dist/src/services/vtex/vtex-catalog-write.js +194 -1
  45. package/dist/src/services/vtex/vtex-catalog-write.js.map +2 -2
  46. package/dist/src/services/vtex/vtex-catalog.js +118 -1
  47. package/dist/src/services/vtex/vtex-catalog.js.map +2 -2
  48. package/dist/src/services/vtex/vtex-pricing-write.js +5 -0
  49. package/dist/src/services/vtex/vtex-pricing-write.js.map +2 -2
  50. package/dist/src/services/vtex/vtex-write.js +67 -1
  51. package/dist/src/services/vtex/vtex-write.js.map +2 -2
  52. package/dist/src/tools/config/list-profiles.js +85 -11
  53. package/dist/src/tools/config/list-profiles.js.map +2 -2
  54. package/dist/src/tools/index.js.map +2 -2
  55. package/dist/src/tools/mercadolibre/__tests__/helpers.test.js +37 -0
  56. package/dist/src/tools/mercadolibre/__tests__/helpers.test.js.map +7 -0
  57. package/dist/src/tools/mercadolibre/__tests__/profile-resolution.test.js +30 -0
  58. package/dist/src/tools/mercadolibre/__tests__/profile-resolution.test.js.map +7 -0
  59. package/dist/src/tools/mercadolibre/profile-resolution.js +4 -50
  60. package/dist/src/tools/mercadolibre/profile-resolution.js.map +2 -2
  61. package/dist/src/tools/meta/__tests__/pagination.test.js +133 -0
  62. package/dist/src/tools/meta/__tests__/pagination.test.js.map +7 -0
  63. package/dist/src/tools/meta/__tests__/profile-access.test.js +262 -0
  64. package/dist/src/tools/meta/__tests__/profile-access.test.js.map +7 -0
  65. package/dist/src/tools/meta/__tests__/read-tools.test.js +722 -0
  66. package/dist/src/tools/meta/__tests__/read-tools.test.js.map +7 -0
  67. package/dist/src/tools/meta/__tests__/schemas.test.js +103 -0
  68. package/dist/src/tools/meta/__tests__/schemas.test.js.map +7 -0
  69. package/dist/src/tools/meta/account-overview.js +37 -19
  70. package/dist/src/tools/meta/account-overview.js.map +2 -2
  71. package/dist/src/tools/meta/ad-account-info.js +31 -6
  72. package/dist/src/tools/meta/ad-account-info.js.map +2 -2
  73. package/dist/src/tools/meta/ads-performance.js +35 -21
  74. package/dist/src/tools/meta/ads-performance.js.map +2 -2
  75. package/dist/src/tools/meta/campaign-performance.js +35 -18
  76. package/dist/src/tools/meta/campaign-performance.js.map +2 -2
  77. package/dist/src/tools/meta/list-accessible-ad-accounts.js +39 -10
  78. package/dist/src/tools/meta/list-accessible-ad-accounts.js.map +2 -2
  79. package/dist/src/tools/meta/list-accessible-businesses.js +37 -13
  80. package/dist/src/tools/meta/list-accessible-businesses.js.map +2 -2
  81. package/dist/src/tools/meta/placement-mix.js +37 -22
  82. package/dist/src/tools/meta/placement-mix.js.map +2 -2
  83. package/dist/src/tools/meta/profile-access.js +215 -0
  84. package/dist/src/tools/meta/profile-access.js.map +7 -0
  85. package/dist/src/tools/meta/schema-helpers.js +19 -0
  86. package/dist/src/tools/meta/schema-helpers.js.map +7 -0
  87. package/dist/src/tools/meta/time-series.js +40 -23
  88. package/dist/src/tools/meta/time-series.js.map +2 -2
  89. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js +233 -0
  90. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js.map +7 -0
  91. package/dist/src/tools/vtex/__tests__/catalog-admin-categories.test.js +649 -0
  92. package/dist/src/tools/vtex/__tests__/catalog-admin-categories.test.js.map +7 -0
  93. package/dist/src/tools/vtex/__tests__/catalog-admin-product-specifications.test.js +339 -0
  94. package/dist/src/tools/vtex/__tests__/catalog-admin-product-specifications.test.js.map +7 -0
  95. package/dist/src/tools/vtex/__tests__/catalog-admin-products-skus.test.js +235 -0
  96. package/dist/src/tools/vtex/__tests__/catalog-admin-products-skus.test.js.map +7 -0
  97. package/dist/src/tools/vtex/__tests__/catalog-navigation-reads.test.js +372 -0
  98. package/dist/src/tools/vtex/__tests__/catalog-navigation-reads.test.js.map +7 -0
  99. package/dist/src/tools/vtex/__tests__/catalog-navigation-surface.test.js +57 -0
  100. package/dist/src/tools/vtex/__tests__/catalog-navigation-surface.test.js.map +7 -0
  101. package/dist/src/tools/vtex/__tests__/write-helpers.test.js +97 -0
  102. package/dist/src/tools/vtex/__tests__/write-helpers.test.js.map +7 -0
  103. package/dist/src/tools/vtex/activate-sku.js +1 -48
  104. package/dist/src/tools/vtex/activate-sku.js.map +2 -2
  105. package/dist/src/tools/vtex/associate-specification.js +3 -54
  106. package/dist/src/tools/vtex/associate-specification.js.map +2 -2
  107. package/dist/src/tools/vtex/attach-catalog-image.js +3 -57
  108. package/dist/src/tools/vtex/attach-catalog-image.js.map +2 -2
  109. package/dist/src/tools/vtex/catalog-admin-batch.js +298 -0
  110. package/dist/src/tools/vtex/catalog-admin-batch.js.map +7 -0
  111. package/dist/src/tools/vtex/catalog-admin-categories.js +542 -0
  112. package/dist/src/tools/vtex/catalog-admin-categories.js.map +7 -0
  113. package/dist/src/tools/vtex/catalog-admin-product-specifications.js +275 -0
  114. package/dist/src/tools/vtex/catalog-admin-product-specifications.js.map +7 -0
  115. package/dist/src/tools/vtex/catalog-admin-products-skus.js +475 -0
  116. package/dist/src/tools/vtex/catalog-admin-products-skus.js.map +7 -0
  117. package/dist/src/tools/vtex/catalog-navigation-reads.js +430 -0
  118. package/dist/src/tools/vtex/catalog-navigation-reads.js.map +7 -0
  119. package/dist/src/tools/vtex/create-brand.js +1 -64
  120. package/dist/src/tools/vtex/create-brand.js.map +2 -2
  121. package/dist/src/tools/vtex/create-category.js +1 -76
  122. package/dist/src/tools/vtex/create-category.js.map +2 -2
  123. package/dist/src/tools/vtex/create-product-with-sku.js +3 -114
  124. package/dist/src/tools/vtex/create-product-with-sku.js.map +2 -2
  125. package/dist/src/tools/vtex/create-specification-value.js +3 -47
  126. package/dist/src/tools/vtex/create-specification-value.js.map +2 -2
  127. package/dist/src/tools/vtex/create-specification.js +1 -80
  128. package/dist/src/tools/vtex/create-specification.js.map +2 -2
  129. package/dist/src/tools/vtex/deactivate-sku.js +1 -48
  130. package/dist/src/tools/vtex/deactivate-sku.js.map +2 -2
  131. package/dist/src/tools/vtex/delete-all-product-specifications.js +9 -0
  132. package/dist/src/tools/vtex/delete-all-product-specifications.js.map +7 -0
  133. package/dist/src/tools/vtex/delete-all-sku-specifications.js +9 -0
  134. package/dist/src/tools/vtex/delete-all-sku-specifications.js.map +7 -0
  135. package/dist/src/tools/vtex/delete-brand.js +9 -0
  136. package/dist/src/tools/vtex/delete-brand.js.map +7 -0
  137. package/dist/src/tools/vtex/delete-catalog-image.js +9 -0
  138. package/dist/src/tools/vtex/delete-catalog-image.js.map +7 -0
  139. package/dist/src/tools/vtex/delete-product-specification.js +9 -0
  140. package/dist/src/tools/vtex/delete-product-specification.js.map +7 -0
  141. package/dist/src/tools/vtex/delete-sku-price.js +55 -0
  142. package/dist/src/tools/vtex/delete-sku-price.js.map +7 -0
  143. package/dist/src/tools/vtex/delete-sku-specification.js +9 -0
  144. package/dist/src/tools/vtex/delete-sku-specification.js.map +7 -0
  145. package/dist/src/tools/vtex/get-brand.js +6 -0
  146. package/dist/src/tools/vtex/get-brand.js.map +7 -0
  147. package/dist/src/tools/vtex/get-category-tree.js +6 -0
  148. package/dist/src/tools/vtex/get-category-tree.js.map +7 -0
  149. package/dist/src/tools/vtex/get-category.js +6 -0
  150. package/dist/src/tools/vtex/get-category.js.map +7 -0
  151. package/dist/src/tools/vtex/get-product-specifications.js +9 -0
  152. package/dist/src/tools/vtex/get-product-specifications.js.map +7 -0
  153. package/dist/src/tools/vtex/get-product.js +6 -0
  154. package/dist/src/tools/vtex/get-product.js.map +7 -0
  155. package/dist/src/tools/vtex/get-sku.js +6 -0
  156. package/dist/src/tools/vtex/get-sku.js.map +7 -0
  157. package/dist/src/tools/vtex/index.js +23 -1
  158. package/dist/src/tools/vtex/index.js.map +2 -2
  159. package/dist/src/tools/vtex/list-brands.js +6 -0
  160. package/dist/src/tools/vtex/list-brands.js.map +7 -0
  161. package/dist/src/tools/vtex/list-categories.js +6 -0
  162. package/dist/src/tools/vtex/list-categories.js.map +7 -0
  163. package/dist/src/tools/vtex/list-products-by-category.js +6 -0
  164. package/dist/src/tools/vtex/list-products-by-category.js.map +7 -0
  165. package/dist/src/tools/vtex/list-products.js +6 -0
  166. package/dist/src/tools/vtex/list-products.js.map +7 -0
  167. package/dist/src/tools/vtex/list-skus-by-product.js +6 -0
  168. package/dist/src/tools/vtex/list-skus-by-product.js.map +7 -0
  169. package/dist/src/tools/vtex/list-specification-groups.js +9 -0
  170. package/dist/src/tools/vtex/list-specification-groups.js.map +7 -0
  171. package/dist/src/tools/vtex/move-category.js +6 -0
  172. package/dist/src/tools/vtex/move-category.js.map +7 -0
  173. package/dist/src/tools/vtex/profile-resolution.js +4 -51
  174. package/dist/src/tools/vtex/profile-resolution.js.map +2 -2
  175. package/dist/src/tools/vtex/update-brand.js +6 -0
  176. package/dist/src/tools/vtex/update-brand.js.map +7 -0
  177. package/dist/src/tools/vtex/update-category.js +6 -0
  178. package/dist/src/tools/vtex/update-category.js.map +7 -0
  179. package/dist/src/tools/vtex/update-product-basic-fields.js +3 -65
  180. package/dist/src/tools/vtex/update-product-basic-fields.js.map +2 -2
  181. package/dist/src/tools/vtex/update-sku-basic-fields.js +1 -87
  182. package/dist/src/tools/vtex/update-sku-basic-fields.js.map +2 -2
  183. package/dist/src/tools/vtex/update-sku-price.js +21 -1
  184. package/dist/src/tools/vtex/update-sku-price.js.map +2 -2
  185. package/dist/src/tools/vtex/write-helpers.js +104 -14
  186. package/dist/src/tools/vtex/write-helpers.js.map +2 -2
  187. package/dist/src/utils/provider-profile-selection.js +117 -0
  188. package/dist/src/utils/provider-profile-selection.js.map +7 -0
  189. package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js +678 -0
  190. package/dist/tests/meli/mercadolibre-tool-handlers-medium-batch4.test.js.map +7 -0
  191. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js +564 -0
  192. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch5.test.js.map +7 -0
  193. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js +387 -0
  194. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch6.test.js.map +7 -0
  195. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js +368 -0
  196. package/dist/tests/meli/mercadolibre-tool-handlers-monster-batch7.test.js.map +7 -0
  197. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js +626 -0
  198. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js.map +7 -0
  199. package/dist/tests/meli/mercadolibre-tool-handlers-write-batch8.test.js +480 -0
  200. package/dist/tests/meli/mercadolibre-tool-handlers-write-batch8.test.js.map +7 -0
  201. package/dist/tests/setup.js +2 -0
  202. package/dist/tests/setup.js.map +7 -0
  203. package/dist/tests/smoke/test-harness.test.js +7 -0
  204. package/dist/tests/smoke/test-harness.test.js.map +7 -0
  205. package/dist/tests/vtex/read-only-utils.test.js +161 -0
  206. package/dist/tests/vtex/read-only-utils.test.js.map +7 -0
  207. package/dist/tests/vtex/vtex-catalog-admin-docs.test.js +24 -0
  208. package/dist/tests/vtex/vtex-catalog-admin-docs.test.js.map +7 -0
  209. package/dist/tests/vtex/vtex-catalog-admin-surface.test.js +30 -0
  210. package/dist/tests/vtex/vtex-catalog-admin-surface.test.js.map +7 -0
  211. package/dist/tests/vtex/vtex-catalog-write-tools.test.js +712 -0
  212. package/dist/tests/vtex/vtex-catalog-write-tools.test.js.map +7 -0
  213. package/dist/tests/vtex/vtex-catalog.service.test.js +51 -0
  214. package/dist/tests/vtex/vtex-catalog.service.test.js.map +7 -0
  215. package/dist/tests/vtex/vtex-inventory-tools.test.js +201 -0
  216. package/dist/tests/vtex/vtex-inventory-tools.test.js.map +7 -0
  217. package/dist/tests/vtex/vtex-logistics.service.test.js +134 -0
  218. package/dist/tests/vtex/vtex-logistics.service.test.js.map +7 -0
  219. package/dist/tests/vtex/vtex-order-details.tool.test.js +141 -0
  220. package/dist/tests/vtex/vtex-order-details.tool.test.js.map +7 -0
  221. package/dist/tests/vtex/vtex-order-write-tools.test.js +483 -0
  222. package/dist/tests/vtex/vtex-order-write-tools.test.js.map +7 -0
  223. package/dist/tests/vtex/vtex-orders-summary.tool.test.js +185 -0
  224. package/dist/tests/vtex/vtex-orders-summary.tool.test.js.map +7 -0
  225. package/dist/tests/vtex/vtex-orders.service.test.js +120 -0
  226. package/dist/tests/vtex/vtex-orders.service.test.js.map +7 -0
  227. package/dist/tests/vtex/vtex-pricing-write-tools.test.js +202 -0
  228. package/dist/tests/vtex/vtex-pricing-write-tools.test.js.map +7 -0
  229. package/dist/tests/vtex/vtex-pricing-write.service.test.js +106 -0
  230. package/dist/tests/vtex/vtex-pricing-write.service.test.js.map +7 -0
  231. package/dist/tests/vtex/vtex-pricing.service.test.js +88 -0
  232. package/dist/tests/vtex/vtex-pricing.service.test.js.map +7 -0
  233. package/dist/tests/vtex/vtex-small-tools.test.js +190 -0
  234. package/dist/tests/vtex/vtex-small-tools.test.js.map +7 -0
  235. package/dist/tests/vtex/vtex-write-simple-tools.test.js +647 -0
  236. package/dist/tests/vtex/vtex-write-simple-tools.test.js.map +7 -0
  237. package/dist/vitest.config.js +15 -0
  238. package/dist/vitest.config.js.map +7 -0
  239. package/package.json +6 -2
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/meta-store.ts"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\nimport { normalizeRequestedAccountIds } from \"../meta/meta-utils.js\";\nimport { decryptSecret } from \"./vtex-crypto.js\";\n\nexport type MetaConnectionStatus =\n\t| \"active\"\n\t| \"disabled\"\n\t| \"expired\"\n\t| \"error\"\n\t| \"pending\";\n\nexport interface MetaOAuthConnectionRecord {\n\tid: string;\n\tencryptedAccessToken: string | null;\n\tscopes: string[];\n\tstatus: MetaConnectionStatus;\n\tmetaUserId: string | null;\n\ttokenExpiresAt: Date | null;\n\tlastValidatedAt: Date | null;\n\tlastError: string | null;\n\tupdatedAt: Date | null;\n}\n\nexport interface ProfileMetaMappingRecord {\n\tprofileId: string;\n\tmetaBusinessId: string | null;\n\tselectedAdAccountIds: string[];\n\tstatus: MetaConnectionStatus;\n\tlastValidatedAt: Date | null;\n\tlastError: string | null;\n\tupdatedAt: Date | null;\n}\n\ninterface MetaOAuthConnectionRow {\n\tid: string;\n\tencrypted_access_token: string | null;\n\tscopes: string[] | null;\n\tstatus: MetaConnectionStatus;\n\tmeta_user_id: string | null;\n\ttoken_expires_at: Date | null;\n\tlast_validated_at: Date | null;\n\tlast_error: string | null;\n\tupdated_at: Date | null;\n}\n\ninterface ProfileMetaMappingRow {\n\tprofile_id: string;\n\tmeta_business_id: string | null;\n\tselected_ad_account_ids: string[] | null;\n\tstatus: MetaConnectionStatus;\n\tlast_validated_at: Date | null;\n\tlast_error: string | null;\n\tupdated_at: Date | null;\n}\n\nfunction isMissingRelationError(error: unknown): boolean {\n\treturn error instanceof Error && /does not exist/i.test(error.message);\n}\n\nfunction normalizeStoredAdAccountIds(\n\taccountIds: string[] | null | undefined,\n): string[] {\n\treturn normalizeRequestedAccountIds(accountIds ?? []) ?? [];\n}\n\nfunction mapMetaOAuthConnection(\n\trow: MetaOAuthConnectionRow,\n): MetaOAuthConnectionRecord {\n\treturn {\n\t\tid: row.id,\n\t\tencryptedAccessToken: row.encrypted_access_token,\n\t\tscopes: Array.isArray(row.scopes) ? row.scopes : [],\n\t\tstatus: row.status,\n\t\tmetaUserId: row.meta_user_id,\n\t\ttokenExpiresAt: row.token_expires_at,\n\t\tlastValidatedAt: row.last_validated_at,\n\t\tlastError: row.last_error,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n\nfunction mapProfileMetaMapping(\n\trow: ProfileMetaMappingRow,\n): ProfileMetaMappingRecord {\n\treturn {\n\t\tprofileId: row.profile_id,\n\t\tmetaBusinessId: row.meta_business_id?.trim() || null,\n\t\tselectedAdAccountIds: normalizeStoredAdAccountIds(\n\t\t\trow.selected_ad_account_ids,\n\t\t),\n\t\tstatus: row.status,\n\t\tlastValidatedAt: row.last_validated_at,\n\t\tlastError: row.last_error,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n\nexport async function getMetaOAuthConnection(): Promise<MetaOAuthConnectionRecord | null> {\n\ttry {\n\t\tconst result = await getDb().query<MetaOAuthConnectionRow>(\n\t\t\t`select id, encrypted_access_token, scopes, status, meta_user_id, token_expires_at, last_validated_at, last_error, updated_at\n from meta_oauth_connections\n where id = 'global'`,\n\t\t);\n\t\tconst row = result.rows[0];\n\t\treturn row ? mapMetaOAuthConnection(row) : null;\n\t} catch (error) {\n\t\tif (isMissingRelationError(error)) return null;\n\t\tthrow error;\n\t}\n}\n\nexport async function getActiveMetaAccessTokenFromStore(): Promise<string> {\n\tconst connection = await getMetaOAuthConnection();\n\n\tif (!connection) {\n\t\tthrow new Error(\n\t\t\t\"Missing active Meta OAuth connection in database. Configure Meta OAuth in the dashboard first.\",\n\t\t);\n\t}\n\n\tif (connection.status !== \"active\") {\n\t\tthrow new Error(\n\t\t\t`Meta OAuth connection is not active (status: ${connection.status}). Validate or reconnect Meta OAuth in the dashboard.`,\n\t\t);\n\t}\n\n\tif (!connection.encryptedAccessToken) {\n\t\tthrow new Error(\n\t\t\t\"Meta OAuth connection is active but has no encrypted access token stored.\",\n\t\t);\n\t}\n\n\ttry {\n\t\treturn decryptSecret(connection.encryptedAccessToken);\n\t} catch (error) {\n\t\tconst message =\n\t\t\terror instanceof Error ? error.message : \"Unknown decryption error\";\n\t\tthrow new Error(\n\t\t\t`Failed to decrypt Meta OAuth access token from database: ${message}`,\n\t\t);\n\t}\n}\n\nexport async function getProfileMetaMapping(\n\tprofileId: string,\n): Promise<ProfileMetaMappingRecord | null> {\n\ttry {\n\t\tconst result = await getDb().query<ProfileMetaMappingRow>(\n\t\t\t`select profile_id, meta_business_id, selected_ad_account_ids, status, last_validated_at, last_error, updated_at\n from profile_meta_mappings\n where profile_id = $1`,\n\t\t\t[profileId],\n\t\t);\n\t\tconst row = result.rows[0];\n\t\treturn row ? mapProfileMetaMapping(row) : null;\n\t} catch (error) {\n\t\tif (isMissingRelationError(error)) return null;\n\t\tthrow error;\n\t}\n}\n\nexport async function listProfileMetaMappings(): Promise<\n\tProfileMetaMappingRecord[]\n> {\n\ttry {\n\t\tconst result = await getDb().query<ProfileMetaMappingRow>(\n\t\t\t`select profile_id, meta_business_id, selected_ad_account_ids, status, last_validated_at, last_error, updated_at\n from profile_meta_mappings`,\n\t\t);\n\t\treturn result.rows.map(mapProfileMetaMapping);\n\t} catch (error) {\n\t\tif (isMissingRelationError(error)) return [];\n\t\tthrow error;\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,oCAAoC;AAC7C,SAAS,qBAAqB;AAqD9B,SAAS,uBAAuB,OAAyB;AACxD,SAAO,iBAAiB,SAAS,kBAAkB,KAAK,MAAM,OAAO;AACtE;AAEA,SAAS,4BACR,YACW;AACX,SAAO,6BAA6B,cAAc,CAAC,CAAC,KAAK,CAAC;AAC3D;AAEA,SAAS,uBACR,KAC4B;AAC5B,SAAO;AAAA,IACN,IAAI,IAAI;AAAA,IACR,sBAAsB,IAAI;AAAA,IAC1B,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AAAA,IAClD,QAAQ,IAAI;AAAA,IACZ,YAAY,IAAI;AAAA,IAChB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EAChB;AACD;AAEA,SAAS,sBACR,KAC2B;AAC3B,SAAO;AAAA,IACN,WAAW,IAAI;AAAA,IACf,gBAAgB,IAAI,kBAAkB,KAAK,KAAK;AAAA,IAChD,sBAAsB;AAAA,MACrB,IAAI;AAAA,IACL;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EAChB;AACD;AAEA,eAAsB,yBAAoE;AACzF,MAAI;AACH,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC5B;AAAA;AAAA;AAAA,IAGD;AACA,UAAM,MAAM,OAAO,KAAK,CAAC;AACzB,WAAO,MAAM,uBAAuB,GAAG,IAAI;AAAA,EAC5C,SAAS,OAAO;AACf,QAAI,uBAAuB,KAAK,EAAG,QAAO;AAC1C,UAAM;AAAA,EACP;AACD;AAEA,eAAsB,oCAAqD;AAC1E,QAAM,aAAa,MAAM,uBAAuB;AAEhD,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,MAAI,WAAW,WAAW,UAAU;AACnC,UAAM,IAAI;AAAA,MACT,gDAAgD,WAAW,MAAM;AAAA,IAClE;AAAA,EACD;AAEA,MAAI,CAAC,WAAW,sBAAsB;AACrC,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,MAAI;AACH,WAAO,cAAc,WAAW,oBAAoB;AAAA,EACrD,SAAS,OAAO;AACf,UAAM,UACL,iBAAiB,QAAQ,MAAM,UAAU;AAC1C,UAAM,IAAI;AAAA,MACT,4DAA4D,OAAO;AAAA,IACpE;AAAA,EACD;AACD;AAEA,eAAsB,sBACrB,WAC2C;AAC3C,MAAI;AACH,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC5B;AAAA;AAAA;AAAA,MAGA,CAAC,SAAS;AAAA,IACX;AACA,UAAM,MAAM,OAAO,KAAK,CAAC;AACzB,WAAO,MAAM,sBAAsB,GAAG,IAAI;AAAA,EAC3C,SAAS,OAAO;AACf,QAAI,uBAAuB,KAAK,EAAG,QAAO;AAC1C,UAAM;AAAA,EACP;AACD;AAEA,eAAsB,0BAEpB;AACD,MAAI;AACH,UAAM,SAAS,MAAM,MAAM,EAAE;AAAA,MAC5B;AAAA;AAAA,IAED;AACA,WAAO,OAAO,KAAK,IAAI,qBAAqB;AAAA,EAC7C,SAAS,OAAO;AACf,QAAI,uBAAuB,KAAK,EAAG,QAAO,CAAC;AAC3C,UAAM;AAAA,EACP;AACD;",
6
+ "names": []
7
+ }
@@ -9,12 +9,10 @@ function requireEnv(name) {
9
9
  function getMetaConfig() {
10
10
  const appId = requireEnv("META_APP_ID");
11
11
  const appSecret = requireEnv("META_APP_SECRET");
12
- const accessToken = requireEnv("META_ACCESS_TOKEN");
13
12
  const apiVersion = process.env.META_API_VERSION?.trim() || "v25.0";
14
13
  return {
15
14
  appId,
16
15
  appSecret,
17
- accessToken,
18
16
  apiVersion,
19
17
  baseUrl: `https://graph.facebook.com/${apiVersion}`
20
18
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/config/meta.ts"],
4
- "sourcesContent": ["import { createHmac } from \"node:crypto\";\n\nfunction requireEnv(name: \"META_APP_ID\" | \"META_APP_SECRET\" | \"META_ACCESS_TOKEN\"): string {\n const value = process.env[name]?.trim();\n if (!value) {\n throw new Error(`Missing required environment variable: ${name}`);\n }\n\n return value;\n}\n\nexport interface MetaConfig {\n appId: string;\n appSecret: string;\n accessToken: string;\n apiVersion: string;\n baseUrl: string;\n}\n\nexport function getMetaConfig(): MetaConfig {\n const appId = requireEnv(\"META_APP_ID\");\n const appSecret = requireEnv(\"META_APP_SECRET\");\n const accessToken = requireEnv(\"META_ACCESS_TOKEN\");\n const apiVersion = process.env.META_API_VERSION?.trim() || \"v25.0\";\n\n return {\n appId,\n appSecret,\n accessToken,\n apiVersion,\n baseUrl: `https://graph.facebook.com/${apiVersion}`,\n };\n}\n\nexport function createMetaAppSecretProof(accessToken: string, appSecret: string): string {\n return createHmac(\"sha256\", appSecret).update(accessToken).digest(\"hex\");\n}\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,WAAW,MAAuE;AACzF,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,EAClE;AAEA,SAAO;AACT;AAUO,SAAS,gBAA4B;AAC1C,QAAM,QAAQ,WAAW,aAAa;AACtC,QAAM,YAAY,WAAW,iBAAiB;AAC9C,QAAM,cAAc,WAAW,mBAAmB;AAClD,QAAM,aAAa,QAAQ,IAAI,kBAAkB,KAAK,KAAK;AAE3D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,8BAA8B,UAAU;AAAA,EACnD;AACF;AAEO,SAAS,yBAAyB,aAAqB,WAA2B;AACvF,SAAO,WAAW,UAAU,SAAS,EAAE,OAAO,WAAW,EAAE,OAAO,KAAK;AACzE;",
4
+ "sourcesContent": ["import { createHmac } from \"node:crypto\";\n\nfunction requireEnv(name: \"META_APP_ID\" | \"META_APP_SECRET\"): string {\n\tconst value = process.env[name]?.trim();\n\tif (!value) {\n\t\tthrow new Error(`Missing required environment variable: ${name}`);\n\t}\n\n\treturn value;\n}\n\nexport interface MetaConfig {\n\tappId: string;\n\tappSecret: string;\n\tapiVersion: string;\n\tbaseUrl: string;\n}\n\nexport function getMetaConfig(): MetaConfig {\n\tconst appId = requireEnv(\"META_APP_ID\");\n\tconst appSecret = requireEnv(\"META_APP_SECRET\");\n\tconst apiVersion = process.env.META_API_VERSION?.trim() || \"v25.0\";\n\n\treturn {\n\t\tappId,\n\t\tappSecret,\n\t\tapiVersion,\n\t\tbaseUrl: `https://graph.facebook.com/${apiVersion}`,\n\t};\n}\n\nexport function createMetaAppSecretProof(\n\taccessToken: string,\n\tappSecret: string,\n): string {\n\treturn createHmac(\"sha256\", appSecret).update(accessToken).digest(\"hex\");\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,WAAW,MAAiD;AACpE,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,OAAO;AACX,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAAA,EACjE;AAEA,SAAO;AACR;AASO,SAAS,gBAA4B;AAC3C,QAAM,QAAQ,WAAW,aAAa;AACtC,QAAM,YAAY,WAAW,iBAAiB;AAC9C,QAAM,aAAa,QAAQ,IAAI,kBAAkB,KAAK,KAAK;AAE3D,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,8BAA8B,UAAU;AAAA,EAClD;AACD;AAEO,SAAS,yBACf,aACA,WACS;AACT,SAAO,WAAW,UAAU,SAAS,EAAE,OAAO,WAAW,EAAE,OAAO,KAAK;AACxE;",
6
6
  "names": []
7
7
  }
@@ -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
+ }