@yoryoboy/bi-mcp 1.16.0 → 1.19.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 (158) hide show
  1. package/README.md +12 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/index.js +179 -6
  4. package/dist/index.js.map +2 -2
  5. package/dist/mcp-use.json +3 -6
  6. package/dist/src/config/__tests__/google-store.test.js +30 -0
  7. package/dist/src/config/__tests__/google-store.test.js.map +7 -0
  8. package/dist/src/config/__tests__/mcp-tool-deadline.test.js +64 -0
  9. package/dist/src/config/__tests__/mcp-tool-deadline.test.js.map +7 -0
  10. package/dist/src/config/google-store.js +18 -0
  11. package/dist/src/config/google-store.js.map +2 -2
  12. package/dist/src/config/mcp-tool-deadline-context.js +34 -0
  13. package/dist/src/config/mcp-tool-deadline-context.js.map +7 -0
  14. package/dist/src/config/mcp-tool-deadline.js +67 -0
  15. package/dist/src/config/mcp-tool-deadline.js.map +7 -0
  16. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js +38 -3
  17. package/dist/src/services/mercadolibre/__tests__/mercadolibre-api.test.js.map +2 -2
  18. package/dist/src/services/mercadolibre/__tests__/mercadolibre-buyer-reconciliation.test.js +226 -0
  19. package/dist/src/services/mercadolibre/__tests__/mercadolibre-buyer-reconciliation.test.js.map +7 -0
  20. package/dist/src/services/mercadolibre/__tests__/mercadolibre-claim-reconciliation.test.js +127 -0
  21. package/dist/src/services/mercadolibre/__tests__/mercadolibre-claim-reconciliation.test.js.map +7 -0
  22. package/dist/src/services/mercadolibre/__tests__/mercadolibre-kits.test.js +103 -0
  23. package/dist/src/services/mercadolibre/__tests__/mercadolibre-kits.test.js.map +7 -0
  24. package/dist/src/services/mercadolibre/__tests__/mercadolibre-order-billing-info.test.js +582 -0
  25. package/dist/src/services/mercadolibre/__tests__/mercadolibre-order-billing-info.test.js.map +7 -0
  26. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js +10 -0
  27. package/dist/src/services/mercadolibre/__tests__/mercadolibre-orders.test.js.map +2 -2
  28. package/dist/src/services/mercadolibre/__tests__/mercadolibre-packs.test.js +36 -0
  29. package/dist/src/services/mercadolibre/__tests__/mercadolibre-packs.test.js.map +7 -0
  30. package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js +137 -0
  31. package/dist/src/services/mercadolibre/__tests__/mercadolibre-payments.test.js.map +7 -0
  32. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-group-ads.test.js +105 -0
  33. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-group-ads.test.js.map +7 -0
  34. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-groups.test.js +143 -0
  35. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-ad-groups.test.js.map +7 -0
  36. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-campaigns.test.js +100 -0
  37. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads-campaigns.test.js.map +7 -0
  38. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads.test.js +350 -0
  39. package/dist/src/services/mercadolibre/__tests__/mercadolibre-product-ads.test.js.map +7 -0
  40. package/dist/src/services/mercadolibre/__tests__/mercadolibre-return-reconciliation.test.js +228 -0
  41. package/dist/src/services/mercadolibre/__tests__/mercadolibre-return-reconciliation.test.js.map +7 -0
  42. package/dist/src/services/mercadolibre/__tests__/mercadolibre-shipping.test.js +58 -0
  43. package/dist/src/services/mercadolibre/__tests__/mercadolibre-shipping.test.js.map +7 -0
  44. package/dist/src/services/mercadolibre/mercadolibre-api.js +15 -3
  45. package/dist/src/services/mercadolibre/mercadolibre-api.js.map +2 -2
  46. package/dist/src/services/mercadolibre/mercadolibre-billing.js +110 -3
  47. package/dist/src/services/mercadolibre/mercadolibre-billing.js.map +2 -2
  48. package/dist/src/services/mercadolibre/mercadolibre-buyer-reconciliation.js +368 -0
  49. package/dist/src/services/mercadolibre/mercadolibre-buyer-reconciliation.js.map +7 -0
  50. package/dist/src/services/mercadolibre/mercadolibre-claim-reconciliation.js +399 -0
  51. package/dist/src/services/mercadolibre/mercadolibre-claim-reconciliation.js.map +7 -0
  52. package/dist/src/services/mercadolibre/mercadolibre-kits.js +55 -0
  53. package/dist/src/services/mercadolibre/mercadolibre-kits.js.map +7 -0
  54. package/dist/src/services/mercadolibre/mercadolibre-order-billing-info.js +392 -0
  55. package/dist/src/services/mercadolibre/mercadolibre-order-billing-info.js.map +7 -0
  56. package/dist/src/services/mercadolibre/mercadolibre-orders.js +8 -8
  57. package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +2 -2
  58. package/dist/src/services/mercadolibre/mercadolibre-packs.js +21 -0
  59. package/dist/src/services/mercadolibre/mercadolibre-packs.js.map +7 -0
  60. package/dist/src/services/mercadolibre/mercadolibre-payments.js +60 -0
  61. package/dist/src/services/mercadolibre/mercadolibre-payments.js.map +7 -0
  62. package/dist/src/services/mercadolibre/mercadolibre-product-ads.js +429 -0
  63. package/dist/src/services/mercadolibre/mercadolibre-product-ads.js.map +7 -0
  64. package/dist/src/services/mercadolibre/mercadolibre-return-reconciliation.js +436 -0
  65. package/dist/src/services/mercadolibre/mercadolibre-return-reconciliation.js.map +7 -0
  66. package/dist/src/services/mercadolibre/mercadolibre-shipping.js +35 -0
  67. package/dist/src/services/mercadolibre/mercadolibre-shipping.js.map +7 -0
  68. package/dist/src/services/vtex/__tests__/vtex-api.test.js +67 -0
  69. package/dist/src/services/vtex/__tests__/vtex-api.test.js.map +7 -0
  70. package/dist/src/services/vtex/vtex-api.js +6 -0
  71. package/dist/src/services/vtex/vtex-api.js.map +2 -2
  72. package/dist/src/tools/config/__tests__/list-profiles.test.js +154 -0
  73. package/dist/src/tools/config/__tests__/list-profiles.test.js.map +7 -0
  74. package/dist/src/tools/config/list-profiles.js +39 -3
  75. package/dist/src/tools/config/list-profiles.js.map +2 -2
  76. package/dist/src/tools/mercadolibre/__tests__/get-order-billing-info.test.js +339 -0
  77. package/dist/src/tools/mercadolibre/__tests__/get-order-billing-info.test.js.map +7 -0
  78. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.js +225 -0
  79. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.js.map +7 -0
  80. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.js +148 -0
  81. package/dist/src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.js.map +7 -0
  82. package/dist/src/tools/mercadolibre/__tests__/product-ads-campaigns.test.js +60 -0
  83. package/dist/src/tools/mercadolibre/__tests__/product-ads-campaigns.test.js.map +7 -0
  84. package/dist/src/tools/mercadolibre/__tests__/product-ads-helpers.test.js +128 -0
  85. package/dist/src/tools/mercadolibre/__tests__/product-ads-helpers.test.js.map +7 -0
  86. package/dist/src/tools/mercadolibre/__tests__/product-ads-tools.test.js +435 -0
  87. package/dist/src/tools/mercadolibre/__tests__/product-ads-tools.test.js.map +7 -0
  88. package/dist/src/tools/mercadolibre/__tests__/reconcile-buyer-data.test.js +31 -0
  89. package/dist/src/tools/mercadolibre/__tests__/reconcile-buyer-data.test.js.map +7 -0
  90. package/dist/src/tools/mercadolibre/__tests__/reconcile-claim-data.test.js +36 -0
  91. package/dist/src/tools/mercadolibre/__tests__/reconcile-claim-data.test.js.map +7 -0
  92. package/dist/src/tools/mercadolibre/__tests__/validate-connection.test.js +205 -0
  93. package/dist/src/tools/mercadolibre/__tests__/validate-connection.test.js.map +7 -0
  94. package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js +313 -0
  95. package/dist/src/tools/mercadolibre/get-billing-provisions-by-order.js.map +7 -0
  96. package/dist/src/tools/mercadolibre/get-bundle-context-by-item.js +176 -0
  97. package/dist/src/tools/mercadolibre/get-bundle-context-by-item.js.map +7 -0
  98. package/dist/src/tools/mercadolibre/get-full-storage-charges.js +183 -0
  99. package/dist/src/tools/mercadolibre/get-full-storage-charges.js.map +7 -0
  100. package/dist/src/tools/mercadolibre/get-order-billing-info.js +189 -0
  101. package/dist/src/tools/mercadolibre/get-order-billing-info.js.map +7 -0
  102. package/dist/src/tools/mercadolibre/get-pack-context-by-order.js +150 -0
  103. package/dist/src/tools/mercadolibre/get-pack-context-by-order.js.map +7 -0
  104. package/dist/src/tools/mercadolibre/get-payment-details.js +322 -0
  105. package/dist/src/tools/mercadolibre/get-payment-details.js.map +7 -0
  106. package/dist/src/tools/mercadolibre/get-product-ads-ad-group-ads.js +144 -0
  107. package/dist/src/tools/mercadolibre/get-product-ads-ad-group-ads.js.map +7 -0
  108. package/dist/src/tools/mercadolibre/get-product-ads-advertisers.js +59 -0
  109. package/dist/src/tools/mercadolibre/get-product-ads-advertisers.js.map +7 -0
  110. package/dist/src/tools/mercadolibre/get-shipping-quote-by-declared-dimensions.js +104 -0
  111. package/dist/src/tools/mercadolibre/get-shipping-quote-by-declared-dimensions.js.map +7 -0
  112. package/dist/src/tools/mercadolibre/index.js +32 -0
  113. package/dist/src/tools/mercadolibre/index.js.map +2 -2
  114. package/dist/src/tools/mercadolibre/product-ads-helpers.js +130 -0
  115. package/dist/src/tools/mercadolibre/product-ads-helpers.js.map +7 -0
  116. package/dist/src/tools/mercadolibre/reconcile-buyer-data.js +27 -0
  117. package/dist/src/tools/mercadolibre/reconcile-buyer-data.js.map +7 -0
  118. package/dist/src/tools/mercadolibre/reconcile-claim-data.js +46 -0
  119. package/dist/src/tools/mercadolibre/reconcile-claim-data.js.map +7 -0
  120. package/dist/src/tools/mercadolibre/reconcile-return-data.js +25 -0
  121. package/dist/src/tools/mercadolibre/reconcile-return-data.js.map +7 -0
  122. package/dist/src/tools/mercadolibre/search-product-ads-ad-groups.js +162 -0
  123. package/dist/src/tools/mercadolibre/search-product-ads-ad-groups.js.map +7 -0
  124. package/dist/src/tools/mercadolibre/search-product-ads-ads.js +216 -0
  125. package/dist/src/tools/mercadolibre/search-product-ads-ads.js.map +7 -0
  126. package/dist/src/tools/mercadolibre/search-product-ads-campaigns.js +116 -0
  127. package/dist/src/tools/mercadolibre/search-product-ads-campaigns.js.map +7 -0
  128. package/dist/src/tools/mercadolibre/validate-connection.js +40 -12
  129. package/dist/src/tools/mercadolibre/validate-connection.js.map +2 -2
  130. package/dist/tests/meli/mercadolibre-billing-provisions.test.js +378 -0
  131. package/dist/tests/meli/mercadolibre-billing-provisions.test.js.map +2 -2
  132. package/dist/tests/meli/mercadolibre-order-billing-info-surface.test.js +123 -0
  133. package/dist/tests/meli/mercadolibre-order-billing-info-surface.test.js.map +7 -0
  134. package/dist/tests/meli/mercadolibre-payment-details.test.js +356 -0
  135. package/dist/tests/meli/mercadolibre-payment-details.test.js.map +7 -0
  136. package/dist/tests/meli/mercadolibre-payments.test.js +62 -0
  137. package/dist/tests/meli/mercadolibre-payments.test.js.map +7 -0
  138. package/dist/tests/meli/mercadolibre-product-ads-ad-groups-surface.test.js +90 -0
  139. package/dist/tests/meli/mercadolibre-product-ads-ad-groups-surface.test.js.map +7 -0
  140. package/dist/tests/meli/mercadolibre-product-ads-campaigns-surface.test.js +72 -0
  141. package/dist/tests/meli/mercadolibre-product-ads-campaigns-surface.test.js.map +7 -0
  142. package/dist/tests/meli/mercadolibre-tool-handlers-pack-kit-shipping.test.js +400 -0
  143. package/dist/tests/meli/mercadolibre-tool-handlers-pack-kit-shipping.test.js.map +7 -0
  144. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js +31 -12
  145. package/dist/tests/meli/mercadolibre-tool-handlers-small.test.js.map +2 -2
  146. package/dist/tests/vtex/vtex-commercial-conditions-docs.test.js +1 -1
  147. package/dist/tests/vtex/vtex-commercial-conditions-docs.test.js.map +1 -1
  148. package/package.json +1 -1
  149. package/dist/src/google-ads/report-utils.js +0 -78
  150. package/dist/src/google-ads/report-utils.js.map +0 -7
  151. package/dist/src/search-console/search-console-utils.js +0 -275
  152. package/dist/src/search-console/search-console-utils.js.map +0 -7
  153. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js +0 -165
  154. package/dist/src/services/vtex/__tests__/vtex-catalog-write-batch.test.js.map +0 -7
  155. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js +0 -233
  156. package/dist/src/tools/vtex/__tests__/catalog-admin-batch.test.js.map +0 -7
  157. package/dist/src/tools/vtex/catalog-admin-batch.js +0 -298
  158. package/dist/src/tools/vtex/catalog-admin-batch.js.map +0 -7
@@ -0,0 +1,148 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { z } from "zod";
3
+ import { MercadoLibreApiError } from "../../../services/mercadolibre/mercadolibre-api.js";
4
+ const objectMock = vi.fn((value) => ({ kind: "object", value }));
5
+ const errorMock = vi.fn((value) => ({ kind: "error", value }));
6
+ const resolveMercadoLibreProfileOrSelectionMock = vi.fn();
7
+ const resolveMercadoLibreProductAdsContextMock = vi.fn();
8
+ const searchMercadoLibreProductAdsAdGroupsMock = vi.fn();
9
+ vi.mock("mcp-use/server", () => ({ object: objectMock, error: errorMock }));
10
+ vi.mock("../profile-resolution.js", () => ({
11
+ resolveMercadoLibreProfileOrSelection: resolveMercadoLibreProfileOrSelectionMock
12
+ }));
13
+ vi.mock("../product-ads-helpers.js", () => ({
14
+ resolveMercadoLibreProductAdsContext: resolveMercadoLibreProductAdsContextMock,
15
+ mercadolibreProductAdsProfileIdSchemaField: z.string().optional(),
16
+ mercadolibreProductAdsSiteIdSchemaField: z.string().optional(),
17
+ mercadolibreProductAdsAdvertiserIdSchemaField: z.string().optional()
18
+ }));
19
+ vi.mock("../../../services/mercadolibre/mercadolibre-product-ads.js", () => ({
20
+ searchMercadoLibreProductAdsAdGroups: searchMercadoLibreProductAdsAdGroupsMock
21
+ }));
22
+ const toolModule = await import("../search-product-ads-ad-groups.js");
23
+ describe("mercadolibre product ads ad-groups tool", () => {
24
+ const input = {
25
+ profileId: "triplim",
26
+ siteId: "MLA",
27
+ advertiserId: "89015",
28
+ dateFrom: "2026-06-01",
29
+ dateTo: "2026-06-30"
30
+ };
31
+ beforeEach(() => vi.clearAllMocks());
32
+ it("validates calendar dates, date ordering, page mode, and documented filter rows at the schema boundary", () => {
33
+ expect(
34
+ toolModule.meliSearchProductAdsAdGroupsSchema.safeParse({
35
+ ...input,
36
+ dateFrom: "2026-06-30",
37
+ dateTo: "2026-06-01",
38
+ mode: "full_universe",
39
+ filters: [{ field: "", value: "active" }]
40
+ }).success
41
+ ).toBe(false);
42
+ expect(
43
+ toolModule.meliSearchProductAdsAdGroupsSchema.safeParse({
44
+ ...input,
45
+ dateFrom: "2026-02-30"
46
+ }).success
47
+ ).toBe(false);
48
+ expect(
49
+ toolModule.meliSearchProductAdsAdGroupsSchema.safeParse({
50
+ ...input,
51
+ filters: [{ field: "status", value: "active" }]
52
+ }).success
53
+ ).toBe(false);
54
+ });
55
+ it("returns nested ad-group rows, metrics summary, and page continuation metadata", async () => {
56
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
57
+ const rows = Array.from({ length: 50 }, (_, index) => ({
58
+ id: `ag-${index + 1}`,
59
+ campaign_id: "campaign-1",
60
+ status: "active",
61
+ metadata: { name: `Group ${index + 1}` },
62
+ metrics: { clicks: index + 1 }
63
+ }));
64
+ searchMercadoLibreProductAdsAdGroupsMock.mockResolvedValueOnce({
65
+ paging: { total: 1499, limit: 50, offset: 0 },
66
+ results: rows,
67
+ metricsSummary: { clicks: 1275 }
68
+ });
69
+ const result = await toolModule.meliSearchProductAdsAdGroupsHandler({
70
+ ...input,
71
+ metrics: ["clicks"],
72
+ metricsSummary: true,
73
+ sort: "desc",
74
+ sortBy: "clicks",
75
+ filters: [{ field: "statuses", value: "active" }],
76
+ limit: 50
77
+ });
78
+ expect(result).toEqual({
79
+ kind: "object",
80
+ value: expect.objectContaining({
81
+ profile_id: "triplim",
82
+ site_id: "MLA",
83
+ advertiser_id: "89015",
84
+ mode: "page",
85
+ results: rows,
86
+ metrics_summary: { clicks: 1275 },
87
+ metadata: expect.objectContaining({
88
+ entity: "ad_groups",
89
+ fetch_mode: "page",
90
+ total: 1499,
91
+ has_more: true,
92
+ next_offset: 50,
93
+ continuation: "Re-run meli_search_product_ads_ad_groups with mode=page and offset=50, limit=50 to continue."
94
+ })
95
+ })
96
+ });
97
+ expect(searchMercadoLibreProductAdsAdGroupsMock).toHaveBeenCalledWith(
98
+ "triplim",
99
+ expect.objectContaining({ filters: { statuses: "active" }, limit: 50, offset: 0 })
100
+ );
101
+ expect(resolveMercadoLibreProductAdsContextMock).not.toHaveBeenCalled();
102
+ });
103
+ it("returns the empty page state with no continuation", async () => {
104
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
105
+ searchMercadoLibreProductAdsAdGroupsMock.mockResolvedValueOnce({
106
+ paging: { total: 0, limit: 50, offset: 0 },
107
+ results: []
108
+ });
109
+ await expect(toolModule.meliSearchProductAdsAdGroupsHandler(input)).resolves.toEqual(expect.objectContaining({
110
+ kind: "object",
111
+ value: expect.objectContaining({
112
+ results: [],
113
+ metadata: expect.objectContaining({ total: 0, returned: 0, has_more: false })
114
+ })
115
+ }));
116
+ });
117
+ it("fails closed without a next offset when an upstream page is underfilled but reports more rows", async () => {
118
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
119
+ searchMercadoLibreProductAdsAdGroupsMock.mockResolvedValueOnce({
120
+ paging: { total: 120, limit: 50, offset: 0 },
121
+ results: [{ id: "ag-1" }]
122
+ });
123
+ const result = await toolModule.meliSearchProductAdsAdGroupsHandler(input);
124
+ expect(result).toEqual(
125
+ expect.objectContaining({
126
+ kind: "object",
127
+ value: expect.objectContaining({
128
+ metadata: expect.objectContaining({
129
+ has_more: false,
130
+ continuation: expect.stringContaining("Do not advance the offset; re-run meli_search_product_ads_ad_groups with mode=page and offset=0, limit=50")
131
+ })
132
+ })
133
+ })
134
+ );
135
+ expect(result.value.metadata).not.toHaveProperty(
136
+ "next_offset"
137
+ );
138
+ });
139
+ it.each([401, 403, 404])("preserves undocumented upstream %s errors through standard handling", async (status) => {
140
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
141
+ searchMercadoLibreProductAdsAdGroupsMock.mockRejectedValueOnce(new MercadoLibreApiError(`upstream ${status}`, status));
142
+ await expect(toolModule.meliSearchProductAdsAdGroupsHandler(input)).resolves.toEqual({
143
+ kind: "error",
144
+ value: `upstream ${status}`
145
+ });
146
+ });
147
+ });
148
+ //# sourceMappingURL=product-ads-ad-groups.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/tools/mercadolibre/__tests__/product-ads-ad-groups.test.ts"],
4
+ "sourcesContent": ["import { beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { z } from \"zod\";\n\nimport { MercadoLibreApiError } from \"../../../services/mercadolibre/mercadolibre-api.js\";\n\nconst objectMock = vi.fn((value) => ({ kind: \"object\", value }));\nconst errorMock = vi.fn((value) => ({ kind: \"error\", value }));\nconst resolveMercadoLibreProfileOrSelectionMock = vi.fn();\nconst resolveMercadoLibreProductAdsContextMock = vi.fn();\nconst searchMercadoLibreProductAdsAdGroupsMock = vi.fn();\n\nvi.mock(\"mcp-use/server\", () => ({ object: objectMock, error: errorMock }));\nvi.mock(\"../profile-resolution.js\", () => ({\n resolveMercadoLibreProfileOrSelection: resolveMercadoLibreProfileOrSelectionMock,\n}));\nvi.mock(\"../product-ads-helpers.js\", () => ({\n resolveMercadoLibreProductAdsContext: resolveMercadoLibreProductAdsContextMock,\n mercadolibreProductAdsProfileIdSchemaField: z.string().optional(),\n mercadolibreProductAdsSiteIdSchemaField: z.string().optional(),\n mercadolibreProductAdsAdvertiserIdSchemaField: z.string().optional(),\n}));\nvi.mock(\"../../../services/mercadolibre/mercadolibre-product-ads.js\", () => ({\n searchMercadoLibreProductAdsAdGroups: searchMercadoLibreProductAdsAdGroupsMock,\n}));\n\nconst toolModule = await import(\"../search-product-ads-ad-groups.js\");\n\ndescribe(\"mercadolibre product ads ad-groups tool\", () => {\n const input = {\n profileId: \"triplim\",\n siteId: \"MLA\",\n advertiserId: \"89015\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n };\n\n beforeEach(() => vi.clearAllMocks());\n\n it(\"validates calendar dates, date ordering, page mode, and documented filter rows at the schema boundary\", () => {\n expect(\n toolModule.meliSearchProductAdsAdGroupsSchema.safeParse({\n ...input,\n dateFrom: \"2026-06-30\",\n dateTo: \"2026-06-01\",\n mode: \"full_universe\",\n filters: [{ field: \"\", value: \"active\" }],\n }).success\n ).toBe(false);\n\n expect(\n toolModule.meliSearchProductAdsAdGroupsSchema.safeParse({\n ...input,\n dateFrom: \"2026-02-30\",\n }).success\n ).toBe(false);\n\n expect(\n toolModule.meliSearchProductAdsAdGroupsSchema.safeParse({\n ...input,\n filters: [{ field: \"status\", value: \"active\" }],\n }).success\n ).toBe(false);\n });\n\n it(\"returns nested ad-group rows, metrics summary, and page continuation metadata\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n const rows = Array.from({ length: 50 }, (_, index) => ({\n id: `ag-${index + 1}`,\n campaign_id: \"campaign-1\",\n status: \"active\",\n metadata: { name: `Group ${index + 1}` },\n metrics: { clicks: index + 1 },\n }));\n searchMercadoLibreProductAdsAdGroupsMock.mockResolvedValueOnce({\n paging: { total: 1499, limit: 50, offset: 0 },\n results: rows,\n metricsSummary: { clicks: 1275 },\n });\n\n const result = await toolModule.meliSearchProductAdsAdGroupsHandler({\n ...input,\n metrics: [\"clicks\"],\n metricsSummary: true,\n sort: \"desc\",\n sortBy: \"clicks\",\n filters: [{ field: \"statuses\", value: \"active\" }],\n limit: 50,\n } as never);\n\n expect(result).toEqual({\n kind: \"object\",\n value: expect.objectContaining({\n profile_id: \"triplim\",\n site_id: \"MLA\",\n advertiser_id: \"89015\",\n mode: \"page\",\n results: rows,\n metrics_summary: { clicks: 1275 },\n metadata: expect.objectContaining({\n entity: \"ad_groups\",\n fetch_mode: \"page\",\n total: 1499,\n has_more: true,\n next_offset: 50,\n continuation: \"Re-run meli_search_product_ads_ad_groups with mode=page and offset=50, limit=50 to continue.\",\n }),\n }),\n });\n expect(searchMercadoLibreProductAdsAdGroupsMock).toHaveBeenCalledWith(\n \"triplim\",\n expect.objectContaining({ filters: { statuses: \"active\" }, limit: 50, offset: 0 })\n );\n expect(resolveMercadoLibreProductAdsContextMock).not.toHaveBeenCalled();\n });\n\n it(\"returns the empty page state with no continuation\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n searchMercadoLibreProductAdsAdGroupsMock.mockResolvedValueOnce({\n paging: { total: 0, limit: 50, offset: 0 },\n results: [],\n });\n\n await expect(toolModule.meliSearchProductAdsAdGroupsHandler(input as never)).resolves.toEqual(expect.objectContaining({\n kind: \"object\",\n value: expect.objectContaining({\n results: [],\n metadata: expect.objectContaining({ total: 0, returned: 0, has_more: false }),\n }),\n }));\n });\n\n it(\"fails closed without a next offset when an upstream page is underfilled but reports more rows\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n searchMercadoLibreProductAdsAdGroupsMock.mockResolvedValueOnce({\n paging: { total: 120, limit: 50, offset: 0 },\n results: [{ id: \"ag-1\" }],\n });\n\n const result = await toolModule.meliSearchProductAdsAdGroupsHandler(input as never);\n\n expect(result).toEqual(\n expect.objectContaining({\n kind: \"object\",\n value: expect.objectContaining({\n metadata: expect.objectContaining({\n has_more: false,\n continuation: expect.stringContaining(\"Do not advance the offset; re-run meli_search_product_ads_ad_groups with mode=page and offset=0, limit=50\"),\n }),\n }),\n })\n );\n expect((result as unknown as { value: { metadata: Record<string, unknown> } }).value.metadata).not.toHaveProperty(\n \"next_offset\"\n );\n });\n\n it.each([401, 403, 404])(\"preserves undocumented upstream %s errors through standard handling\", async (status) => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n searchMercadoLibreProductAdsAdGroupsMock.mockRejectedValueOnce(new MercadoLibreApiError(`upstream ${status}`, status));\n\n await expect(toolModule.meliSearchProductAdsAdGroupsHandler(input as never)).resolves.toEqual({\n kind: \"error\",\n value: `upstream ${status}`,\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,60 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { z } from "zod";
3
+ import { MercadoLibreApiError } from "../../../services/mercadolibre/mercadolibre-api.js";
4
+ const objectMock = vi.fn((value) => ({ kind: "object", value }));
5
+ const errorMock = vi.fn((value) => ({ kind: "error", value }));
6
+ const resolveProfileMock = vi.fn();
7
+ const resolveContextMock = vi.fn();
8
+ const searchCampaignsMock = vi.fn();
9
+ vi.mock("mcp-use/server", () => ({ object: objectMock, error: errorMock }));
10
+ vi.mock("../profile-resolution.js", () => ({ resolveMercadoLibreProfileOrSelection: resolveProfileMock }));
11
+ vi.mock("../product-ads-helpers.js", () => ({
12
+ resolveMercadoLibreProductAdsContext: resolveContextMock,
13
+ mercadolibreProductAdsProfileIdSchemaField: z.string().optional(),
14
+ mercadolibreProductAdsSiteIdSchemaField: z.string().optional(),
15
+ mercadolibreProductAdsAdvertiserIdSchemaField: z.string().optional()
16
+ }));
17
+ vi.mock("../../../services/mercadolibre/mercadolibre-product-ads.js", () => ({
18
+ searchMercadoLibreProductAdsCampaigns: searchCampaignsMock
19
+ }));
20
+ const toolModule = await import("../search-product-ads-campaigns.js");
21
+ describe("mercadolibre product ads campaigns tool", () => {
22
+ const input = { profileId: "triplim", siteId: "MLA", advertiserId: "89015", dateFrom: "2026-06-01", dateTo: "2026-06-30" };
23
+ beforeEach(() => vi.clearAllMocks());
24
+ it("validates real calendar dates, ordering, and page mode", () => {
25
+ expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, dateFrom: "2026-02-30" }).success).toBe(false);
26
+ expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, dateFrom: "2026-06-30", dateTo: "2026-06-01" }).success).toBe(false);
27
+ expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, mode: "all" }).success).toBe(false);
28
+ expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, limit: 0 }).success).toBe(false);
29
+ });
30
+ it("returns a raw page and campaign-to-ad-groups chaining guidance", async () => {
31
+ resolveProfileMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
32
+ const results = [{ id: "campaign-1", strategy: { type: "manual" }, budget: { amount: 1e3 } }];
33
+ searchCampaignsMock.mockResolvedValueOnce({ paging: { total: 5, limit: 1, offset: 0 }, results });
34
+ await expect(toolModule.meliSearchProductAdsCampaignsHandler({ ...input, limit: 1 })).resolves.toEqual({
35
+ kind: "object",
36
+ value: expect.objectContaining({
37
+ results,
38
+ metadata: expect.objectContaining({ entity: "campaigns", next_offset: 1, ad_groups_hint: expect.stringContaining("campaign id") })
39
+ })
40
+ });
41
+ });
42
+ it("logs structured campaign discovery observations from the feature boundary", async () => {
43
+ const infoSpy = vi.spyOn(console, "info").mockImplementation(() => void 0);
44
+ resolveProfileMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
45
+ searchCampaignsMock.mockImplementationOnce(async (_profile, _params, options) => {
46
+ options.onObservation({ event: "retry", attempt: 1, elapsed_ms: 250, status: 503, reason: "retryable_error" });
47
+ return { paging: { total: 0, limit: 1, offset: 0 }, results: [] };
48
+ });
49
+ await toolModule.meliSearchProductAdsCampaignsHandler({ ...input, limit: 1 });
50
+ expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('"event":"mercadolibre.product_ads.campaigns.discovery"'));
51
+ expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('"reason":"retryable_error"'));
52
+ infoSpy.mockRestore();
53
+ });
54
+ it.each([401, 403, 404])("preserves upstream %s errors", async (status) => {
55
+ resolveProfileMock.mockResolvedValueOnce({ ok: true, value: { profileId: "triplim" } });
56
+ searchCampaignsMock.mockRejectedValueOnce(new MercadoLibreApiError(`upstream ${status}`, status));
57
+ await expect(toolModule.meliSearchProductAdsCampaignsHandler(input)).resolves.toEqual({ kind: "error", value: `upstream ${status}` });
58
+ });
59
+ });
60
+ //# sourceMappingURL=product-ads-campaigns.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/tools/mercadolibre/__tests__/product-ads-campaigns.test.ts"],
4
+ "sourcesContent": ["import { beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { z } from \"zod\";\n\nimport { MercadoLibreApiError } from \"../../../services/mercadolibre/mercadolibre-api.js\";\n\nconst objectMock = vi.fn((value) => ({ kind: \"object\", value }));\nconst errorMock = vi.fn((value) => ({ kind: \"error\", value }));\nconst resolveProfileMock = vi.fn();\nconst resolveContextMock = vi.fn();\nconst searchCampaignsMock = vi.fn();\n\nvi.mock(\"mcp-use/server\", () => ({ object: objectMock, error: errorMock }));\nvi.mock(\"../profile-resolution.js\", () => ({ resolveMercadoLibreProfileOrSelection: resolveProfileMock }));\nvi.mock(\"../product-ads-helpers.js\", () => ({\n resolveMercadoLibreProductAdsContext: resolveContextMock,\n mercadolibreProductAdsProfileIdSchemaField: z.string().optional(),\n mercadolibreProductAdsSiteIdSchemaField: z.string().optional(),\n mercadolibreProductAdsAdvertiserIdSchemaField: z.string().optional(),\n}));\nvi.mock(\"../../../services/mercadolibre/mercadolibre-product-ads.js\", () => ({\n searchMercadoLibreProductAdsCampaigns: searchCampaignsMock,\n}));\n\nconst toolModule = await import(\"../search-product-ads-campaigns.js\");\n\ndescribe(\"mercadolibre product ads campaigns tool\", () => {\n const input = { profileId: \"triplim\", siteId: \"MLA\", advertiserId: \"89015\", dateFrom: \"2026-06-01\", dateTo: \"2026-06-30\" };\n beforeEach(() => vi.clearAllMocks());\n\n it(\"validates real calendar dates, ordering, and page mode\", () => {\n expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, dateFrom: \"2026-02-30\" }).success).toBe(false);\n expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, dateFrom: \"2026-06-30\", dateTo: \"2026-06-01\" }).success).toBe(false);\n expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, mode: \"all\" }).success).toBe(false);\n expect(toolModule.meliSearchProductAdsCampaignsSchema.safeParse({ ...input, limit: 0 }).success).toBe(false);\n });\n\n it(\"returns a raw page and campaign-to-ad-groups chaining guidance\", async () => {\n resolveProfileMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n const results = [{ id: \"campaign-1\", strategy: { type: \"manual\" }, budget: { amount: 1000 } }];\n searchCampaignsMock.mockResolvedValueOnce({ paging: { total: 5, limit: 1, offset: 0 }, results });\n\n await expect(toolModule.meliSearchProductAdsCampaignsHandler({ ...input, limit: 1 } as never)).resolves.toEqual({\n kind: \"object\", value: expect.objectContaining({\n results,\n metadata: expect.objectContaining({ entity: \"campaigns\", next_offset: 1, ad_groups_hint: expect.stringContaining(\"campaign id\") }),\n }),\n });\n });\n\n it(\"logs structured campaign discovery observations from the feature boundary\", async () => {\n const infoSpy = vi.spyOn(console, \"info\").mockImplementation(() => undefined);\n resolveProfileMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n searchCampaignsMock.mockImplementationOnce(async (_profile, _params, options) => {\n options.onObservation({ event: \"retry\", attempt: 1, elapsed_ms: 250, status: 503, reason: \"retryable_error\" });\n return { paging: { total: 0, limit: 1, offset: 0 }, results: [] };\n });\n\n await toolModule.meliSearchProductAdsCampaignsHandler({ ...input, limit: 1 } as never);\n\n expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('\"event\":\"mercadolibre.product_ads.campaigns.discovery\"'));\n expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('\"reason\":\"retryable_error\"'));\n infoSpy.mockRestore();\n });\n\n it.each([401, 403, 404])(\"preserves upstream %s errors\", async (status) => {\n resolveProfileMock.mockResolvedValueOnce({ ok: true, value: { profileId: \"triplim\" } });\n searchCampaignsMock.mockRejectedValueOnce(new MercadoLibreApiError(`upstream ${status}`, status));\n await expect(toolModule.meliSearchProductAdsCampaignsHandler(input as never)).resolves.toEqual({ kind: \"error\", value: `upstream ${status}` });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,128 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ const objectMock = vi.fn((value) => ({ kind: "object", value }));
3
+ const resolveMercadoLibreProfileOrSelectionMock = vi.fn();
4
+ const listMercadoLibreProductAdsAdvertisersMock = vi.fn();
5
+ vi.mock("mcp-use/server", () => ({
6
+ object: objectMock
7
+ }));
8
+ vi.mock("../profile-resolution.js", () => ({
9
+ resolveMercadoLibreProfileOrSelection: resolveMercadoLibreProfileOrSelectionMock
10
+ }));
11
+ vi.mock("../../../services/mercadolibre/mercadolibre-product-ads.js", () => ({
12
+ listMercadoLibreProductAdsAdvertisers: listMercadoLibreProductAdsAdvertisersMock
13
+ }));
14
+ const helpersModule = await import("../product-ads-helpers.js");
15
+ describe("mercadolibre product ads helpers", () => {
16
+ it("passes through shared profile selection responses", async () => {
17
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
18
+ ok: false,
19
+ response: { kind: "object", value: { selection_required: true } }
20
+ });
21
+ await expect(
22
+ helpersModule.resolveMercadoLibreProductAdsContext({ profileId: "profile-1" })
23
+ ).resolves.toEqual({
24
+ ok: false,
25
+ response: { kind: "object", value: { selection_required: true } }
26
+ });
27
+ });
28
+ it("auto-resolves a single available advertiser when no context is provided", async () => {
29
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
30
+ ok: true,
31
+ value: { profileId: "profile-1" }
32
+ });
33
+ listMercadoLibreProductAdsAdvertisersMock.mockResolvedValueOnce([
34
+ {
35
+ advertiser_id: "adv-1",
36
+ advertiser_site_id: "MLA",
37
+ account_name: "Main account",
38
+ business_name: "Main business",
39
+ status: "active"
40
+ }
41
+ ]);
42
+ await expect(helpersModule.resolveMercadoLibreProductAdsContext({ profileId: "profile-1" })).resolves.toEqual(
43
+ {
44
+ ok: true,
45
+ value: {
46
+ profileId: "profile-1",
47
+ siteId: "MLA",
48
+ advertiserId: "adv-1",
49
+ advertiser: {
50
+ advertiser_id: "adv-1",
51
+ advertiser_site_id: "MLA",
52
+ account_name: "Main account",
53
+ business_name: "Main business",
54
+ status: "active"
55
+ },
56
+ availableAdvertisers: [
57
+ {
58
+ advertiser_id: "adv-1",
59
+ advertiser_site_id: "MLA",
60
+ account_name: "Main account",
61
+ business_name: "Main business",
62
+ status: "active"
63
+ }
64
+ ],
65
+ resolutionMode: "auto_single_match"
66
+ }
67
+ }
68
+ );
69
+ });
70
+ it("guides discovery without asking for impossible advertiser pairs when none are available", async () => {
71
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
72
+ ok: true,
73
+ value: { profileId: "profile-1" }
74
+ });
75
+ listMercadoLibreProductAdsAdvertisersMock.mockResolvedValueOnce([]);
76
+ const result = await helpersModule.resolveMercadoLibreProductAdsContext({ profileId: "profile-1" });
77
+ expect(result).toEqual({
78
+ ok: false,
79
+ response: {
80
+ kind: "object",
81
+ value: expect.objectContaining({
82
+ selection_required: true,
83
+ reason: "missing_advertiser_context",
84
+ profile_id: "profile-1",
85
+ message: expect.stringContaining("No Product Ads advertisers"),
86
+ resolution_hint: "Re-run advertiser discovery after the profile exposes Product Ads advertisers."
87
+ })
88
+ }
89
+ });
90
+ if (!result.ok) {
91
+ expect(result.response.value).not.toHaveProperty("required_fields");
92
+ }
93
+ });
94
+ it("returns a guided selection response when the advertiser context is incomplete", async () => {
95
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
96
+ ok: true,
97
+ value: { profileId: "profile-1" }
98
+ });
99
+ listMercadoLibreProductAdsAdvertisersMock.mockResolvedValueOnce([
100
+ {
101
+ advertiser_id: "adv-1",
102
+ advertiser_site_id: "MLA"
103
+ },
104
+ {
105
+ advertiser_id: "adv-2",
106
+ advertiser_site_id: "MLB"
107
+ }
108
+ ]);
109
+ await expect(
110
+ helpersModule.resolveMercadoLibreProductAdsContext({
111
+ profileId: "profile-1",
112
+ siteId: "MLA"
113
+ })
114
+ ).resolves.toEqual({
115
+ ok: false,
116
+ response: {
117
+ kind: "object",
118
+ value: expect.objectContaining({
119
+ selection_required: true,
120
+ reason: "incomplete_advertiser_context",
121
+ profile_id: "profile-1",
122
+ resolution_hint: "If you need help choosing a pair, first run the advertiser discovery tool, then pass the selected siteId and advertiserId to the page-mode ads search tool."
123
+ })
124
+ }
125
+ });
126
+ });
127
+ });
128
+ //# sourceMappingURL=product-ads-helpers.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/tools/mercadolibre/__tests__/product-ads-helpers.test.ts"],
4
+ "sourcesContent": ["import { describe, expect, it, vi } from \"vitest\";\n\nconst objectMock = vi.fn((value) => ({ kind: \"object\", value }));\nconst resolveMercadoLibreProfileOrSelectionMock = vi.fn();\nconst listMercadoLibreProductAdsAdvertisersMock = vi.fn();\n\nvi.mock(\"mcp-use/server\", () => ({\n object: objectMock,\n}));\n\nvi.mock(\"../profile-resolution.js\", () => ({\n resolveMercadoLibreProfileOrSelection: resolveMercadoLibreProfileOrSelectionMock,\n}));\n\nvi.mock(\"../../../services/mercadolibre/mercadolibre-product-ads.js\", () => ({\n listMercadoLibreProductAdsAdvertisers: listMercadoLibreProductAdsAdvertisersMock,\n}));\n\nconst helpersModule = await import(\"../product-ads-helpers.js\");\n\ndescribe(\"mercadolibre product ads helpers\", () => {\n it(\"passes through shared profile selection responses\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: false,\n response: { kind: \"object\", value: { selection_required: true } },\n });\n\n await expect(\n helpersModule.resolveMercadoLibreProductAdsContext({ profileId: \"profile-1\" })\n ).resolves.toEqual({\n ok: false,\n response: { kind: \"object\", value: { selection_required: true } },\n });\n });\n\n it(\"auto-resolves a single available advertiser when no context is provided\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n listMercadoLibreProductAdsAdvertisersMock.mockResolvedValueOnce([\n {\n advertiser_id: \"adv-1\",\n advertiser_site_id: \"MLA\",\n account_name: \"Main account\",\n business_name: \"Main business\",\n status: \"active\",\n },\n ]);\n\n await expect(helpersModule.resolveMercadoLibreProductAdsContext({ profileId: \"profile-1\" })).resolves.toEqual(\n {\n ok: true,\n value: {\n profileId: \"profile-1\",\n siteId: \"MLA\",\n advertiserId: \"adv-1\",\n advertiser: {\n advertiser_id: \"adv-1\",\n advertiser_site_id: \"MLA\",\n account_name: \"Main account\",\n business_name: \"Main business\",\n status: \"active\",\n },\n availableAdvertisers: [\n {\n advertiser_id: \"adv-1\",\n advertiser_site_id: \"MLA\",\n account_name: \"Main account\",\n business_name: \"Main business\",\n status: \"active\",\n },\n ],\n resolutionMode: \"auto_single_match\",\n },\n }\n );\n });\n\n it(\"guides discovery without asking for impossible advertiser pairs when none are available\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n listMercadoLibreProductAdsAdvertisersMock.mockResolvedValueOnce([]);\n\n const result = await helpersModule.resolveMercadoLibreProductAdsContext({ profileId: \"profile-1\" });\n\n expect(result).toEqual({\n ok: false,\n response: {\n kind: \"object\",\n value: expect.objectContaining({\n selection_required: true,\n reason: \"missing_advertiser_context\",\n profile_id: \"profile-1\",\n message: expect.stringContaining(\"No Product Ads advertisers\"),\n resolution_hint:\n \"Re-run advertiser discovery after the profile exposes Product Ads advertisers.\",\n }),\n },\n });\n if (!result.ok) {\n expect(result.response.value).not.toHaveProperty(\"required_fields\");\n }\n });\n\n it(\"returns a guided selection response when the advertiser context is incomplete\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n listMercadoLibreProductAdsAdvertisersMock.mockResolvedValueOnce([\n {\n advertiser_id: \"adv-1\",\n advertiser_site_id: \"MLA\",\n },\n {\n advertiser_id: \"adv-2\",\n advertiser_site_id: \"MLB\",\n },\n ]);\n\n await expect(\n helpersModule.resolveMercadoLibreProductAdsContext({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n })\n ).resolves.toEqual({\n ok: false,\n response: {\n kind: \"object\",\n value: expect.objectContaining({\n selection_required: true,\n reason: \"incomplete_advertiser_context\",\n profile_id: \"profile-1\",\n resolution_hint:\n \"If you need help choosing a pair, first run the advertiser discovery tool, then pass the selected siteId and advertiserId to the page-mode ads search tool.\",\n }),\n },\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }