@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,339 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ const { errorMock, objectMock, getBillingInfoMock, getSaleBillingInfoMock, resolveProfileMock } = vi.hoisted(() => ({
3
+ errorMock: vi.fn((message) => ({ kind: "error", message })),
4
+ objectMock: vi.fn((payload) => ({ kind: "object", payload })),
5
+ getBillingInfoMock: vi.fn(),
6
+ getSaleBillingInfoMock: vi.fn(),
7
+ resolveProfileMock: vi.fn()
8
+ }));
9
+ vi.mock("mcp-use/server", () => ({ error: errorMock, object: objectMock }));
10
+ vi.mock("../../../services/mercadolibre/mercadolibre-api.js", () => ({
11
+ MercadoLibreApiError: class MercadoLibreApiError extends Error {
12
+ constructor(message, status) {
13
+ super(message);
14
+ this.status = status;
15
+ }
16
+ }
17
+ }));
18
+ vi.mock("../../../services/mercadolibre/mercadolibre-order-billing-info.js", () => ({
19
+ getMercadoLibreOrderBillingInfo: getBillingInfoMock,
20
+ getMercadoLibreSaleBillingInfo: getSaleBillingInfoMock
21
+ }));
22
+ vi.mock("../profile-resolution.js", () => ({ resolveMercadoLibreProfileOrSelection: resolveProfileMock }));
23
+ import {
24
+ meliGetOrderBillingInfoHandler,
25
+ meliGetOrderBillingInfoSchema
26
+ } from "../get-order-billing-info.js";
27
+ import { MercadoLibreApiError } from "../../../services/mercadolibre/mercadolibre-api.js";
28
+ describe("meliGetOrderBillingInfoHandler", () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ resolveProfileMock.mockResolvedValue({ ok: true, value: { profileId: "triplim" } });
32
+ });
33
+ it("validates the explicit profile and a single order ID", () => {
34
+ expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: "triplim", orderId: "123" })).not.toThrow();
35
+ expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: "triplim", saleReference: "123" })).not.toThrow();
36
+ expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: "triplim", orderId: " " })).toThrow();
37
+ expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: "triplim", orderId: "123", saleReference: "123" })).toThrow();
38
+ });
39
+ it("returns only per-member fiscal projections for a pack sale reference", async () => {
40
+ getSaleBillingInfoMock.mockResolvedValue({
41
+ resolutionKind: "pack",
42
+ packId: "pack-1",
43
+ metadata: { memberCount: 2, accessibleCount: 2, availableCount: 1, unavailableCount: 1, failedCount: 0, partialCoverage: true, recoveryGuidance: "Retry unavailable members." },
44
+ members: [
45
+ { orderId: "order-1", accessible: true, result: { available: true, billingInfo: { buyer: { billing_info: { name: "Ada", last_name: "Lovelace", identification: { type: "CUIT", number: "20123456789" }, address: { street_name: "Main", street_number: "10", city_name: "CABA", state: "CABA", zip_code: "1000", country_id: "AR" }, taxes: { taxpayer_type: { id: "ri", description: "RI" } } }, nickname: "pack-buyer-must-not-leak" } } } },
46
+ { orderId: "order-2", accessible: true, result: { available: false, reason: "order_seller_ownership_mismatch" } }
47
+ ]
48
+ });
49
+ const result = await meliGetOrderBillingInfoHandler({ profileId: "triplim", saleReference: "pack-1" });
50
+ const serialized = JSON.stringify(result);
51
+ expect(result).toMatchObject({
52
+ kind: "object",
53
+ payload: { metadata: expect.objectContaining({ resolution_kind: "pack", member_count: 2, partial_coverage: true }) }
54
+ });
55
+ expect(serialized).toContain("20123456789");
56
+ expect(serialized).not.toContain("pack-buyer-must-not-leak");
57
+ expect(getSaleBillingInfoMock).toHaveBeenCalledWith("triplim", "pack-1");
58
+ });
59
+ it("uses the same snake_case pack metadata contract for unavailable packs", async () => {
60
+ getSaleBillingInfoMock.mockResolvedValue({
61
+ resolutionKind: "pack",
62
+ packId: "pack-1",
63
+ unavailableReason: "pack_operation_deadline_exceeded",
64
+ metadata: {
65
+ memberCount: 0,
66
+ validMemberCount: 0,
67
+ invalidMemberCount: 0,
68
+ accessibleCount: 0,
69
+ availableCount: 0,
70
+ unavailableCount: 0,
71
+ failedCount: 0,
72
+ partialCoverage: true,
73
+ deadlineExceeded: true,
74
+ recoveryGuidance: "Retry the same sale reference."
75
+ },
76
+ members: []
77
+ });
78
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", saleReference: "pack-1" })).resolves.toEqual({
79
+ kind: "object",
80
+ payload: {
81
+ metadata: expect.objectContaining({
82
+ member_count: 0,
83
+ valid_member_count: 0,
84
+ invalid_member_count: 0,
85
+ accessible_count: 0,
86
+ available_count: 0,
87
+ unavailable_count: 0,
88
+ failed_count: 0,
89
+ partial_coverage: true,
90
+ deadline_exceeded: true,
91
+ recovery_guidance: "Retry the same sale reference."
92
+ }),
93
+ members: []
94
+ }
95
+ });
96
+ expect(objectMock).toHaveBeenLastCalledWith({
97
+ metadata: expect.not.objectContaining({ memberCount: expect.anything(), deadlineExceeded: expect.anything() }),
98
+ members: []
99
+ });
100
+ });
101
+ it("surfaces only safe retry diagnostics for retry-exhausted pack members", async () => {
102
+ getSaleBillingInfoMock.mockResolvedValue({
103
+ resolutionKind: "pack",
104
+ packId: "pack-1",
105
+ metadata: { memberCount: 1, validMemberCount: 1, invalidMemberCount: 0, accessibleCount: 0, availableCount: 0, unavailableCount: 0, failedCount: 1, partialCoverage: true, deadlineExceeded: false, recoveryGuidance: "Retry later." },
106
+ members: [{ orderId: "order-1", accessible: false, failureReason: "upstream_unavailable", attempts: 3, retryable: true }]
107
+ });
108
+ const result = await meliGetOrderBillingInfoHandler({ profileId: "triplim", saleReference: "pack-1" });
109
+ expect(result).toMatchObject({
110
+ kind: "object",
111
+ payload: { members: [{ metadata: { order_id: "order-1", reason: "upstream_unavailable", attempts: 3, retryable: true } }] }
112
+ });
113
+ expect(JSON.stringify(result)).not.toContain("raw-upstream");
114
+ });
115
+ it("returns only the validated fiscal projection and provenance", async () => {
116
+ getBillingInfoMock.mockResolvedValue({
117
+ available: true,
118
+ billingInfo: {
119
+ buyer: {
120
+ billing_info: {
121
+ name: "Ada",
122
+ last_name: "Lovelace",
123
+ identification: { type: "CUIT", number: "20123456789", extra: "excluded" },
124
+ address: {
125
+ street_name: "Main",
126
+ street_number: "10",
127
+ city_name: "Buenos Aires",
128
+ state: "CABA",
129
+ zip_code: "1000",
130
+ country_id: "AR",
131
+ phone: "excluded"
132
+ },
133
+ taxes: { taxpayer_type: { id: "responsable_inscripto", description: "Responsable Inscripto" } }
134
+ },
135
+ nickname: "excluded"
136
+ },
137
+ unvalidated: "excluded"
138
+ }
139
+ });
140
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
141
+ kind: "object",
142
+ payload: {
143
+ metadata: {
144
+ profile_id: "triplim",
145
+ order_id: "123",
146
+ fiscal_data_status: "available",
147
+ field_provenance: {
148
+ buyer: "billing-info.buyer.billing_info",
149
+ billing_address: "billing-info.buyer.billing_info.address",
150
+ taxpayer_type: "billing-info.buyer.billing_info.taxes.taxpayer_type"
151
+ },
152
+ available_fields: [
153
+ "buyer.name",
154
+ "buyer.last_name",
155
+ "buyer.identification.type",
156
+ "buyer.identification.number",
157
+ "billing_address.street",
158
+ "billing_address.city",
159
+ "billing_address.state",
160
+ "billing_address.zip_code",
161
+ "billing_address.country",
162
+ "taxpayer_type.id",
163
+ "taxpayer_type.description"
164
+ ]
165
+ },
166
+ fiscal: {
167
+ buyer: {
168
+ name: "Ada",
169
+ last_name: "Lovelace",
170
+ identification: { type: "CUIT", number: "20123456789" }
171
+ },
172
+ billing_address: {
173
+ street: "Main 10",
174
+ city: "Buenos Aires",
175
+ state: "CABA",
176
+ zip_code: "1000",
177
+ country: "AR"
178
+ },
179
+ taxpayer_type: { id: "responsable_inscripto", description: "Responsable Inscripto" }
180
+ }
181
+ }
182
+ });
183
+ });
184
+ it("maps an upstream state code/name object to the public state display value", async () => {
185
+ getBillingInfoMock.mockResolvedValue({
186
+ available: true,
187
+ billingInfo: {
188
+ buyer: {
189
+ billing_info: {
190
+ address: {
191
+ state: { code: "AR-C", name: "Ciudad Aut\xF3noma de Buenos Aires" }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ });
197
+ const result = await meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "synthetic-order-1" });
198
+ expect(result).toEqual({
199
+ kind: "object",
200
+ payload: {
201
+ metadata: expect.objectContaining({
202
+ fiscal_data_status: "available",
203
+ available_fields: ["billing_address.state"]
204
+ }),
205
+ fiscal: {
206
+ billing_address: {
207
+ state: "Ciudad Aut\xF3noma de Buenos Aires"
208
+ }
209
+ }
210
+ }
211
+ });
212
+ });
213
+ it("does not fabricate optional fiscal fields when MercadoLibre omits them", async () => {
214
+ getBillingInfoMock.mockResolvedValue({
215
+ available: true,
216
+ billingInfo: { buyer: { billing_info: { name: "Ada", address: { city_name: "CABA" } } } }
217
+ });
218
+ const result = await meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" });
219
+ expect(result).toEqual({
220
+ kind: "object",
221
+ payload: {
222
+ metadata: expect.any(Object),
223
+ fiscal: { buyer: { name: "Ada" }, billing_address: { city: "CABA" } }
224
+ }
225
+ });
226
+ });
227
+ it("reports missing order billing info as unavailable", async () => {
228
+ getBillingInfoMock.mockResolvedValue({ available: false, reason: "order_buyer_billing_info_missing" });
229
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
230
+ kind: "object",
231
+ payload: {
232
+ metadata: {
233
+ profile_id: "triplim",
234
+ order_id: "123",
235
+ fiscal_data_status: "unavailable",
236
+ reason: "order_buyer_billing_info_missing",
237
+ provenance: "order.buyer.billing_info"
238
+ }
239
+ }
240
+ });
241
+ });
242
+ it("reports malformed billing payloads as unavailable without a fiscal object", async () => {
243
+ getBillingInfoMock.mockResolvedValue({ available: false, reason: "billing_info_payload_malformed" });
244
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
245
+ kind: "object",
246
+ payload: {
247
+ metadata: {
248
+ profile_id: "triplim",
249
+ order_id: "123",
250
+ fiscal_data_status: "unavailable",
251
+ reason: "billing_info_payload_malformed",
252
+ provenance: "order.buyer.billing_info"
253
+ }
254
+ }
255
+ });
256
+ });
257
+ it("does not project fiscal data when fiscal fields are malformed", async () => {
258
+ getBillingInfoMock.mockResolvedValue({ available: false, reason: "billing_info_fiscal_fields_malformed" });
259
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
260
+ kind: "object",
261
+ payload: {
262
+ metadata: {
263
+ profile_id: "triplim",
264
+ order_id: "123",
265
+ fiscal_data_status: "unavailable",
266
+ reason: "billing_info_fiscal_fields_malformed",
267
+ provenance: "order.buyer.billing_info"
268
+ }
269
+ }
270
+ });
271
+ });
272
+ it("does not project fiscal data when a required fiscal field is missing", async () => {
273
+ getBillingInfoMock.mockResolvedValue({ available: false, reason: "billing_info_fiscal_fields_missing" });
274
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
275
+ kind: "object",
276
+ payload: {
277
+ metadata: {
278
+ profile_id: "triplim",
279
+ order_id: "123",
280
+ fiscal_data_status: "unavailable",
281
+ reason: "billing_info_fiscal_fields_missing",
282
+ provenance: "order.buyer.billing_info"
283
+ }
284
+ }
285
+ });
286
+ expect(objectMock).toHaveBeenLastCalledWith({
287
+ metadata: expect.not.objectContaining({ fiscal: expect.anything() })
288
+ });
289
+ });
290
+ it("does not project fiscal PII when seller ownership does not match the selected profile", async () => {
291
+ getBillingInfoMock.mockResolvedValue({ available: false, reason: "order_seller_ownership_mismatch" });
292
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
293
+ kind: "object",
294
+ payload: {
295
+ metadata: {
296
+ profile_id: "triplim",
297
+ order_id: "123",
298
+ fiscal_data_status: "unavailable",
299
+ reason: "order_seller_ownership_mismatch",
300
+ provenance: "order.buyer.billing_info"
301
+ }
302
+ }
303
+ });
304
+ expect(objectMock).toHaveBeenLastCalledWith({
305
+ metadata: expect.not.objectContaining({ fiscal: expect.anything() })
306
+ });
307
+ });
308
+ it("returns safe actionable context for a terminal missing order", async () => {
309
+ const upstreamError = Object.assign(new MercadoLibreApiError("raw URL /orders/123 and buyer-tax-id-should-not-leak", 404), {
310
+ resource: "order",
311
+ attempts: 1,
312
+ retryable: false
313
+ });
314
+ getBillingInfoMock.mockRejectedValue(upstreamError);
315
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
316
+ kind: "error",
317
+ message: "MercadoLibre order billing info unavailable: reason=order_not_found; status_class=http_4xx; status=404; attempts=1; retryable=false."
318
+ });
319
+ });
320
+ it("distinguishes retry-exhausted upstream unavailability with safe recovery diagnostics", async () => {
321
+ const upstreamError = Object.assign(
322
+ new MercadoLibreApiError("raw URL /orders/123 and buyer-tax-id-should-not-leak", 503),
323
+ { resource: "order", attempts: 2, retryable: true }
324
+ );
325
+ getBillingInfoMock.mockRejectedValue(upstreamError);
326
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
327
+ kind: "error",
328
+ message: "MercadoLibre order billing info unavailable: reason=upstream_unavailable; status_class=http_5xx; status=503; attempts=2; retryable=true."
329
+ });
330
+ });
331
+ it("keeps unknown upstream errors generic and safe", async () => {
332
+ getBillingInfoMock.mockRejectedValue(new Error("buyer-tax-id-should-not-leak https://api.example/orders/123"));
333
+ await expect(meliGetOrderBillingInfoHandler({ profileId: "triplim", orderId: "123" })).resolves.toEqual({
334
+ kind: "error",
335
+ message: "MercadoLibre order billing info unavailable."
336
+ });
337
+ });
338
+ });
339
+ //# sourceMappingURL=get-order-billing-info.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/tools/mercadolibre/__tests__/get-order-billing-info.test.ts"],
4
+ "sourcesContent": ["import { beforeEach, describe, expect, it, vi } from \"vitest\";\n\nconst { errorMock, objectMock, getBillingInfoMock, getSaleBillingInfoMock, resolveProfileMock } = vi.hoisted(() => ({\n errorMock: vi.fn((message: string) => ({ kind: \"error\", message })),\n objectMock: vi.fn((payload: unknown) => ({ kind: \"object\", payload })),\n getBillingInfoMock: vi.fn(),\n getSaleBillingInfoMock: vi.fn(),\n resolveProfileMock: vi.fn(),\n}));\n\nvi.mock(\"mcp-use/server\", () => ({ error: errorMock, object: objectMock }));\nvi.mock(\"../../../services/mercadolibre/mercadolibre-api.js\", () => ({\n MercadoLibreApiError: class MercadoLibreApiError extends Error {\n constructor(message: string, public readonly status?: number) {\n super(message);\n }\n },\n}));\nvi.mock(\"../../../services/mercadolibre/mercadolibre-order-billing-info.js\", () => ({\n getMercadoLibreOrderBillingInfo: getBillingInfoMock,\n getMercadoLibreSaleBillingInfo: getSaleBillingInfoMock,\n}));\nvi.mock(\"../profile-resolution.js\", () => ({ resolveMercadoLibreProfileOrSelection: resolveProfileMock }));\n\nimport {\n meliGetOrderBillingInfoHandler,\n meliGetOrderBillingInfoSchema,\n} from \"../get-order-billing-info.js\";\nimport { MercadoLibreApiError } from \"../../../services/mercadolibre/mercadolibre-api.js\";\n\ndescribe(\"meliGetOrderBillingInfoHandler\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n resolveProfileMock.mockResolvedValue({ ok: true, value: { profileId: \"triplim\" } });\n });\n\n it(\"validates the explicit profile and a single order ID\", () => {\n expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: \"triplim\", orderId: \"123\" })).not.toThrow();\n expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: \"triplim\", saleReference: \"123\" })).not.toThrow();\n expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: \"triplim\", orderId: \" \" })).toThrow();\n expect(() => meliGetOrderBillingInfoSchema.parse({ profileId: \"triplim\", orderId: \"123\", saleReference: \"123\" })).toThrow();\n });\n\n it(\"returns only per-member fiscal projections for a pack sale reference\", async () => {\n getSaleBillingInfoMock.mockResolvedValue({\n resolutionKind: \"pack\",\n packId: \"pack-1\",\n metadata: { memberCount: 2, accessibleCount: 2, availableCount: 1, unavailableCount: 1, failedCount: 0, partialCoverage: true, recoveryGuidance: \"Retry unavailable members.\" },\n members: [\n { orderId: \"order-1\", accessible: true, result: { available: true, billingInfo: { buyer: { billing_info: { name: \"Ada\", last_name: \"Lovelace\", identification: { type: \"CUIT\", number: \"20123456789\" }, address: { street_name: \"Main\", street_number: \"10\", city_name: \"CABA\", state: \"CABA\", zip_code: \"1000\", country_id: \"AR\" }, taxes: { taxpayer_type: { id: \"ri\", description: \"RI\" } } }, nickname: \"pack-buyer-must-not-leak\" } } } },\n { orderId: \"order-2\", accessible: true, result: { available: false, reason: \"order_seller_ownership_mismatch\" } },\n ],\n });\n\n const result = await meliGetOrderBillingInfoHandler({ profileId: \"triplim\", saleReference: \"pack-1\" });\n const serialized = JSON.stringify(result);\n\n expect(result).toMatchObject({\n kind: \"object\",\n payload: { metadata: expect.objectContaining({ resolution_kind: \"pack\", member_count: 2, partial_coverage: true }) },\n });\n expect(serialized).toContain(\"20123456789\");\n expect(serialized).not.toContain(\"pack-buyer-must-not-leak\");\n expect(getSaleBillingInfoMock).toHaveBeenCalledWith(\"triplim\", \"pack-1\");\n });\n\n it(\"uses the same snake_case pack metadata contract for unavailable packs\", async () => {\n getSaleBillingInfoMock.mockResolvedValue({\n resolutionKind: \"pack\",\n packId: \"pack-1\",\n unavailableReason: \"pack_operation_deadline_exceeded\",\n metadata: {\n memberCount: 0,\n validMemberCount: 0,\n invalidMemberCount: 0,\n accessibleCount: 0,\n availableCount: 0,\n unavailableCount: 0,\n failedCount: 0,\n partialCoverage: true,\n deadlineExceeded: true,\n recoveryGuidance: \"Retry the same sale reference.\",\n },\n members: [],\n });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", saleReference: \"pack-1\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: expect.objectContaining({\n member_count: 0,\n valid_member_count: 0,\n invalid_member_count: 0,\n accessible_count: 0,\n available_count: 0,\n unavailable_count: 0,\n failed_count: 0,\n partial_coverage: true,\n deadline_exceeded: true,\n recovery_guidance: \"Retry the same sale reference.\",\n }),\n members: [],\n },\n });\n expect(objectMock).toHaveBeenLastCalledWith({\n metadata: expect.not.objectContaining({ memberCount: expect.anything(), deadlineExceeded: expect.anything() }),\n members: [],\n });\n });\n\n it(\"surfaces only safe retry diagnostics for retry-exhausted pack members\", async () => {\n getSaleBillingInfoMock.mockResolvedValue({\n resolutionKind: \"pack\",\n packId: \"pack-1\",\n metadata: { memberCount: 1, validMemberCount: 1, invalidMemberCount: 0, accessibleCount: 0, availableCount: 0, unavailableCount: 0, failedCount: 1, partialCoverage: true, deadlineExceeded: false, recoveryGuidance: \"Retry later.\" },\n members: [{ orderId: \"order-1\", accessible: false, failureReason: \"upstream_unavailable\", attempts: 3, retryable: true }],\n });\n\n const result = await meliGetOrderBillingInfoHandler({ profileId: \"triplim\", saleReference: \"pack-1\" });\n\n expect(result).toMatchObject({\n kind: \"object\",\n payload: { members: [{ metadata: { order_id: \"order-1\", reason: \"upstream_unavailable\", attempts: 3, retryable: true } }] },\n });\n expect(JSON.stringify(result)).not.toContain(\"raw-upstream\");\n });\n\n it(\"returns only the validated fiscal projection and provenance\", async () => {\n getBillingInfoMock.mockResolvedValue({\n available: true,\n billingInfo: {\n buyer: {\n billing_info: {\n name: \"Ada\",\n last_name: \"Lovelace\",\n identification: { type: \"CUIT\", number: \"20123456789\", extra: \"excluded\" },\n address: {\n street_name: \"Main\",\n street_number: \"10\",\n city_name: \"Buenos Aires\",\n state: \"CABA\",\n zip_code: \"1000\",\n country_id: \"AR\",\n phone: \"excluded\",\n },\n taxes: { taxpayer_type: { id: \"responsable_inscripto\", description: \"Responsable Inscripto\" } },\n },\n nickname: \"excluded\",\n },\n unvalidated: \"excluded\",\n },\n });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: {\n profile_id: \"triplim\",\n order_id: \"123\",\n fiscal_data_status: \"available\",\n field_provenance: {\n buyer: \"billing-info.buyer.billing_info\",\n billing_address: \"billing-info.buyer.billing_info.address\",\n taxpayer_type: \"billing-info.buyer.billing_info.taxes.taxpayer_type\",\n },\n available_fields: [\n \"buyer.name\",\n \"buyer.last_name\",\n \"buyer.identification.type\",\n \"buyer.identification.number\",\n \"billing_address.street\",\n \"billing_address.city\",\n \"billing_address.state\",\n \"billing_address.zip_code\",\n \"billing_address.country\",\n \"taxpayer_type.id\",\n \"taxpayer_type.description\",\n ],\n },\n fiscal: {\n buyer: {\n name: \"Ada\",\n last_name: \"Lovelace\",\n identification: { type: \"CUIT\", number: \"20123456789\" },\n },\n billing_address: {\n street: \"Main 10\",\n city: \"Buenos Aires\",\n state: \"CABA\",\n zip_code: \"1000\",\n country: \"AR\",\n },\n taxpayer_type: { id: \"responsable_inscripto\", description: \"Responsable Inscripto\" },\n },\n },\n });\n });\n\n it(\"maps an upstream state code/name object to the public state display value\", async () => {\n getBillingInfoMock.mockResolvedValue({\n available: true,\n billingInfo: {\n buyer: {\n billing_info: {\n address: {\n state: { code: \"AR-C\", name: \"Ciudad Aut\u00F3noma de Buenos Aires\" },\n },\n },\n },\n },\n });\n\n const result = await meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"synthetic-order-1\" });\n\n expect(result).toEqual({\n kind: \"object\",\n payload: {\n metadata: expect.objectContaining({\n fiscal_data_status: \"available\",\n available_fields: [\"billing_address.state\"],\n }),\n fiscal: {\n billing_address: {\n state: \"Ciudad Aut\u00F3noma de Buenos Aires\",\n },\n },\n },\n });\n });\n\n it(\"does not fabricate optional fiscal fields when MercadoLibre omits them\", async () => {\n getBillingInfoMock.mockResolvedValue({\n available: true,\n billingInfo: { buyer: { billing_info: { name: \"Ada\", address: { city_name: \"CABA\" } } } },\n });\n\n const result = await meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" });\n expect(result).toEqual({\n kind: \"object\",\n payload: {\n metadata: expect.any(Object),\n fiscal: { buyer: { name: \"Ada\" }, billing_address: { city: \"CABA\" } },\n },\n });\n });\n\n it(\"reports missing order billing info as unavailable\", async () => {\n getBillingInfoMock.mockResolvedValue({ available: false, reason: \"order_buyer_billing_info_missing\" });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: {\n profile_id: \"triplim\",\n order_id: \"123\",\n fiscal_data_status: \"unavailable\",\n reason: \"order_buyer_billing_info_missing\",\n provenance: \"order.buyer.billing_info\",\n },\n },\n });\n });\n\n it(\"reports malformed billing payloads as unavailable without a fiscal object\", async () => {\n getBillingInfoMock.mockResolvedValue({ available: false, reason: \"billing_info_payload_malformed\" });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: {\n profile_id: \"triplim\",\n order_id: \"123\",\n fiscal_data_status: \"unavailable\",\n reason: \"billing_info_payload_malformed\",\n provenance: \"order.buyer.billing_info\",\n },\n },\n });\n });\n\n it(\"does not project fiscal data when fiscal fields are malformed\", async () => {\n getBillingInfoMock.mockResolvedValue({ available: false, reason: \"billing_info_fiscal_fields_malformed\" });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: {\n profile_id: \"triplim\",\n order_id: \"123\",\n fiscal_data_status: \"unavailable\",\n reason: \"billing_info_fiscal_fields_malformed\",\n provenance: \"order.buyer.billing_info\",\n },\n },\n });\n });\n\n it(\"does not project fiscal data when a required fiscal field is missing\", async () => {\n getBillingInfoMock.mockResolvedValue({ available: false, reason: \"billing_info_fiscal_fields_missing\" });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: {\n profile_id: \"triplim\",\n order_id: \"123\",\n fiscal_data_status: \"unavailable\",\n reason: \"billing_info_fiscal_fields_missing\",\n provenance: \"order.buyer.billing_info\",\n },\n },\n });\n expect(objectMock).toHaveBeenLastCalledWith({\n metadata: expect.not.objectContaining({ fiscal: expect.anything() }),\n });\n });\n\n it(\"does not project fiscal PII when seller ownership does not match the selected profile\", async () => {\n getBillingInfoMock.mockResolvedValue({ available: false, reason: \"order_seller_ownership_mismatch\" });\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"object\",\n payload: {\n metadata: {\n profile_id: \"triplim\",\n order_id: \"123\",\n fiscal_data_status: \"unavailable\",\n reason: \"order_seller_ownership_mismatch\",\n provenance: \"order.buyer.billing_info\",\n },\n },\n });\n expect(objectMock).toHaveBeenLastCalledWith({\n metadata: expect.not.objectContaining({ fiscal: expect.anything() }),\n });\n });\n\n it(\"returns safe actionable context for a terminal missing order\", async () => {\n const upstreamError = Object.assign(new MercadoLibreApiError(\"raw URL /orders/123 and buyer-tax-id-should-not-leak\", 404), {\n resource: \"order\",\n attempts: 1,\n retryable: false,\n });\n getBillingInfoMock.mockRejectedValue(upstreamError);\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"error\",\n message:\n \"MercadoLibre order billing info unavailable: reason=order_not_found; status_class=http_4xx; status=404; attempts=1; retryable=false.\",\n });\n });\n\n it(\"distinguishes retry-exhausted upstream unavailability with safe recovery diagnostics\", async () => {\n const upstreamError = Object.assign(\n new MercadoLibreApiError(\"raw URL /orders/123 and buyer-tax-id-should-not-leak\", 503),\n { resource: \"order\", attempts: 2, retryable: true }\n );\n getBillingInfoMock.mockRejectedValue(upstreamError);\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"error\",\n message:\n \"MercadoLibre order billing info unavailable: reason=upstream_unavailable; status_class=http_5xx; status=503; attempts=2; retryable=true.\",\n });\n });\n\n it(\"keeps unknown upstream errors generic and safe\", async () => {\n getBillingInfoMock.mockRejectedValue(new Error(\"buyer-tax-id-should-not-leak https://api.example/orders/123\"));\n\n await expect(meliGetOrderBillingInfoHandler({ profileId: \"triplim\", orderId: \"123\" })).resolves.toEqual({\n kind: \"error\",\n message: \"MercadoLibre order billing info unavailable.\",\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,225 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { MercadoLibreApiError } from "../../../services/mercadolibre/mercadolibre-api.js";
3
+ const objectMock = vi.fn((value) => ({ kind: "object", value }));
4
+ const errorMock = vi.fn((value) => ({ kind: "error", value }));
5
+ const resolveMercadoLibreProfileOrSelectionMock = vi.fn();
6
+ const searchMercadoLibreProductAdsAdGroupAdsMock = vi.fn();
7
+ vi.mock("mcp-use/server", () => ({
8
+ object: objectMock,
9
+ error: errorMock
10
+ }));
11
+ vi.mock("../profile-resolution.js", () => ({
12
+ resolveMercadoLibreProfileOrSelection: resolveMercadoLibreProfileOrSelectionMock
13
+ }));
14
+ vi.mock("../../../services/mercadolibre/mercadolibre-product-ads.js", () => ({
15
+ searchMercadoLibreProductAdsAdGroupAds: searchMercadoLibreProductAdsAdGroupAdsMock
16
+ }));
17
+ const toolsModule = await import("../get-product-ads-ad-group-ads.js");
18
+ describe("mercadolibre product ads ad group ads tool", () => {
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ });
22
+ function buildAdGroupAdsRows(count) {
23
+ return Array.from({ length: count }, (_, index) => {
24
+ const adNumber = index + 1;
25
+ return {
26
+ item_id: `item-${adNumber}`,
27
+ title: `Item ${adNumber}`,
28
+ status: adNumber % 2 === 0 ? "paused" : "active",
29
+ campaign_id: "campaign-1",
30
+ campaign_name: "Campaign one",
31
+ ad_group_id: "ad-group-1",
32
+ ad_group_name: "Ad group one",
33
+ channel: "mshops",
34
+ aggregation_type: "daily",
35
+ metrics: {
36
+ direct_amount: adNumber * 10,
37
+ indirect_amount: adNumber,
38
+ total_amount: adNumber * 11,
39
+ clicks: adNumber * 2,
40
+ impressions: adNumber * 20,
41
+ cost: adNumber * 3,
42
+ advertising_items_quantity: 1,
43
+ currency_id: "ARS"
44
+ }
45
+ };
46
+ });
47
+ }
48
+ it("returns a guided selection response when profile selection is required", async () => {
49
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
50
+ ok: false,
51
+ response: { kind: "object", value: { selection_required: true } }
52
+ });
53
+ await expect(
54
+ toolsModule.meliGetProductAdsAdGroupAdsHandler({
55
+ profileId: "profile-1",
56
+ siteId: "MLA",
57
+ adGroupId: "ad-group-1",
58
+ dateFrom: "2026-06-01",
59
+ dateTo: "2026-06-30",
60
+ metrics: ["direct_amount"]
61
+ })
62
+ ).resolves.toEqual({ kind: "object", value: { selection_required: true } });
63
+ });
64
+ it("rejects invalid calendar dates, date ranges, and blank adGroupId values at the schema boundary", () => {
65
+ const parsed = toolsModule.meliGetProductAdsAdGroupAdsSchema.safeParse({
66
+ profileId: "profile-1",
67
+ siteId: "MLA",
68
+ adGroupId: " ",
69
+ dateFrom: "2026-02-30",
70
+ dateTo: "2026-02-01",
71
+ metrics: ["direct_amount"]
72
+ });
73
+ expect(parsed.success).toBe(false);
74
+ if (!parsed.success) {
75
+ expect(parsed.error.issues).toEqual(
76
+ expect.arrayContaining([
77
+ expect.objectContaining({ path: ["adGroupId"] }),
78
+ expect.objectContaining({ path: ["dateFrom"], message: "dateFrom must be a valid calendar date." }),
79
+ expect.objectContaining({ path: ["dateTo"], message: "dateTo must be on or after dateFrom." })
80
+ ])
81
+ );
82
+ }
83
+ });
84
+ it("returns an actionable not-found error when the ad group is stale or invalid", async () => {
85
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
86
+ ok: true,
87
+ value: { profileId: "profile-1" }
88
+ });
89
+ searchMercadoLibreProductAdsAdGroupAdsMock.mockRejectedValueOnce(new MercadoLibreApiError("not found", 404));
90
+ await expect(
91
+ toolsModule.meliGetProductAdsAdGroupAdsHandler({
92
+ profileId: "profile-1",
93
+ siteId: "MLA",
94
+ adGroupId: "ad-group-404",
95
+ dateFrom: "2026-06-01",
96
+ dateTo: "2026-06-30",
97
+ metrics: ["direct_amount"]
98
+ })
99
+ ).resolves.toEqual({
100
+ kind: "error",
101
+ value: 'MercadoLibre Product Ads ad group "ad-group-404" was not found for siteId "MLA" in profile "profile-1". Verify the adGroupId or run meli_search_product_ads_ad_groups to re-discover Product Ads ad groups if the account changed.'
102
+ });
103
+ expect(searchMercadoLibreProductAdsAdGroupAdsMock).toHaveBeenCalledTimes(1);
104
+ });
105
+ it("translates non-404 upstream failures through the standard MercadoLibre error formatter", async () => {
106
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
107
+ ok: true,
108
+ value: { profileId: "profile-1" }
109
+ });
110
+ searchMercadoLibreProductAdsAdGroupAdsMock.mockRejectedValueOnce(new Error("refresh_token=secret exploded"));
111
+ await expect(
112
+ toolsModule.meliGetProductAdsAdGroupAdsHandler({
113
+ profileId: "profile-1",
114
+ siteId: "MLA",
115
+ adGroupId: "ad-group-1",
116
+ dateFrom: "2026-06-01",
117
+ dateTo: "2026-06-30",
118
+ metrics: ["direct_amount"]
119
+ })
120
+ ).resolves.toEqual({
121
+ kind: "error",
122
+ value: "refresh_token=[redacted] exploded"
123
+ });
124
+ });
125
+ it("returns ad group ads rows with paging metadata and nested metrics intact", async () => {
126
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
127
+ ok: true,
128
+ value: { profileId: "profile-1" }
129
+ });
130
+ searchMercadoLibreProductAdsAdGroupAdsMock.mockResolvedValueOnce({
131
+ paging: { total: 150, limit: 50, offset: 0 },
132
+ results: buildAdGroupAdsRows(50)
133
+ });
134
+ const result = await toolsModule.meliGetProductAdsAdGroupAdsHandler({
135
+ profileId: "profile-1",
136
+ siteId: "MLA",
137
+ adGroupId: "ad-group-1",
138
+ dateFrom: "2026-06-01",
139
+ dateTo: "2026-06-30",
140
+ metrics: ["direct_amount", "clicks"],
141
+ channel: "mshops",
142
+ campaignId: "campaign-1",
143
+ aggregationType: "daily",
144
+ limit: 25,
145
+ offset: 0
146
+ });
147
+ expect(result).toEqual({
148
+ kind: "object",
149
+ value: expect.objectContaining({
150
+ profile_id: "profile-1",
151
+ site_id: "MLA",
152
+ ad_group_id: "ad-group-1",
153
+ product: "PADS",
154
+ mode: "page",
155
+ paging: { total: 150, limit: 50, offset: 0 },
156
+ results: buildAdGroupAdsRows(50),
157
+ metadata: expect.objectContaining({
158
+ entity: "ad_group_ads",
159
+ fetch_mode: "page",
160
+ total: 150,
161
+ requested_limit: 25,
162
+ requested_offset: 0,
163
+ effective_limit: 50,
164
+ effective_offset: 0,
165
+ returned: 50,
166
+ has_more: true,
167
+ next_offset: 50,
168
+ continuation: "Re-run meli_get_product_ads_ad_group_ads with offset=50, limit=50 to continue."
169
+ })
170
+ })
171
+ });
172
+ expect(resolveMercadoLibreProfileOrSelectionMock).toHaveBeenCalledWith("profile-1");
173
+ expect(searchMercadoLibreProductAdsAdGroupAdsMock).toHaveBeenCalledWith(
174
+ "profile-1",
175
+ expect.objectContaining({
176
+ siteId: "MLA",
177
+ adGroupId: "ad-group-1",
178
+ dateFrom: "2026-06-01",
179
+ dateTo: "2026-06-30",
180
+ metrics: ["direct_amount", "clicks"],
181
+ channel: "mshops",
182
+ campaignId: "campaign-1",
183
+ aggregationType: "daily",
184
+ limit: 25,
185
+ offset: 0
186
+ })
187
+ );
188
+ });
189
+ it("marks an underfilled non-terminal page as not having a reliable continuation", async () => {
190
+ resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({
191
+ ok: true,
192
+ value: { profileId: "profile-1" }
193
+ });
194
+ searchMercadoLibreProductAdsAdGroupAdsMock.mockResolvedValueOnce({
195
+ paging: { total: 150, limit: 50, offset: 0 },
196
+ results: buildAdGroupAdsRows(1).slice(0, 1)
197
+ });
198
+ const result = await toolsModule.meliGetProductAdsAdGroupAdsHandler({
199
+ profileId: "profile-1",
200
+ siteId: "MLA",
201
+ adGroupId: "ad-group-1",
202
+ dateFrom: "2026-06-01",
203
+ dateTo: "2026-06-30",
204
+ metrics: ["direct_amount"],
205
+ limit: 25,
206
+ offset: 0
207
+ });
208
+ expect(result).toEqual({
209
+ kind: "object",
210
+ value: expect.objectContaining({
211
+ metadata: expect.objectContaining({
212
+ requested_limit: 25,
213
+ effective_limit: 50,
214
+ returned: 1,
215
+ has_more: false,
216
+ continuation: "The returned page had fewer rows than the effective limit, so page-mode continuation is not reliable for this response."
217
+ })
218
+ })
219
+ });
220
+ expect(result.value.metadata).not.toHaveProperty(
221
+ "next_offset"
222
+ );
223
+ });
224
+ });
225
+ //# sourceMappingURL=product-ads-ad-group-ads.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/tools/mercadolibre/__tests__/product-ads-ad-group-ads.test.ts"],
4
+ "sourcesContent": ["import { beforeEach, describe, expect, it, vi } from \"vitest\";\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 searchMercadoLibreProductAdsAdGroupAdsMock = vi.fn();\n\nvi.mock(\"mcp-use/server\", () => ({\n object: objectMock,\n error: errorMock,\n}));\n\nvi.mock(\"../profile-resolution.js\", () => ({\n resolveMercadoLibreProfileOrSelection: resolveMercadoLibreProfileOrSelectionMock,\n}));\n\nvi.mock(\"../../../services/mercadolibre/mercadolibre-product-ads.js\", () => ({\n searchMercadoLibreProductAdsAdGroupAds: searchMercadoLibreProductAdsAdGroupAdsMock,\n}));\n\nconst toolsModule = await import(\"../get-product-ads-ad-group-ads.js\");\n\ndescribe(\"mercadolibre product ads ad group ads tool\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n function buildAdGroupAdsRows(count: number) {\n return Array.from({ length: count }, (_, index) => {\n const adNumber = index + 1;\n\n return {\n item_id: `item-${adNumber}`,\n title: `Item ${adNumber}`,\n status: adNumber % 2 === 0 ? \"paused\" : \"active\",\n campaign_id: \"campaign-1\",\n campaign_name: \"Campaign one\",\n ad_group_id: \"ad-group-1\",\n ad_group_name: \"Ad group one\",\n channel: \"mshops\",\n aggregation_type: \"daily\",\n metrics: {\n direct_amount: adNumber * 10,\n indirect_amount: adNumber,\n total_amount: adNumber * 11,\n clicks: adNumber * 2,\n impressions: adNumber * 20,\n cost: adNumber * 3,\n advertising_items_quantity: 1,\n currency_id: \"ARS\",\n },\n };\n });\n }\n\n it(\"returns a guided selection response when profile selection is required\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: false,\n response: { kind: \"object\", value: { selection_required: true } },\n });\n\n await expect(\n toolsModule.meliGetProductAdsAdGroupAdsHandler({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n adGroupId: \"ad-group-1\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n metrics: [\"direct_amount\"],\n })\n ).resolves.toEqual({ kind: \"object\", value: { selection_required: true } });\n });\n\n it(\"rejects invalid calendar dates, date ranges, and blank adGroupId values at the schema boundary\", () => {\n const parsed = toolsModule.meliGetProductAdsAdGroupAdsSchema.safeParse({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n adGroupId: \" \",\n dateFrom: \"2026-02-30\",\n dateTo: \"2026-02-01\",\n metrics: [\"direct_amount\"],\n });\n\n expect(parsed.success).toBe(false);\n if (!parsed.success) {\n expect(parsed.error.issues).toEqual(\n expect.arrayContaining([\n expect.objectContaining({ path: [\"adGroupId\"] }),\n expect.objectContaining({ path: [\"dateFrom\"], message: \"dateFrom must be a valid calendar date.\" }),\n expect.objectContaining({ path: [\"dateTo\"], message: \"dateTo must be on or after dateFrom.\" }),\n ])\n );\n }\n });\n\n it(\"returns an actionable not-found error when the ad group is stale or invalid\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n searchMercadoLibreProductAdsAdGroupAdsMock.mockRejectedValueOnce(new MercadoLibreApiError(\"not found\", 404));\n\n await expect(\n toolsModule.meliGetProductAdsAdGroupAdsHandler({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n adGroupId: \"ad-group-404\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n metrics: [\"direct_amount\"],\n } as never)\n ).resolves.toEqual({\n kind: \"error\",\n value:\n 'MercadoLibre Product Ads ad group \"ad-group-404\" was not found for siteId \"MLA\" in profile \"profile-1\". Verify the adGroupId or run meli_search_product_ads_ad_groups to re-discover Product Ads ad groups if the account changed.',\n });\n\n expect(searchMercadoLibreProductAdsAdGroupAdsMock).toHaveBeenCalledTimes(1);\n });\n\n it(\"translates non-404 upstream failures through the standard MercadoLibre error formatter\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n searchMercadoLibreProductAdsAdGroupAdsMock.mockRejectedValueOnce(new Error(\"refresh_token=secret exploded\"));\n\n await expect(\n toolsModule.meliGetProductAdsAdGroupAdsHandler({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n adGroupId: \"ad-group-1\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n metrics: [\"direct_amount\"],\n } as never)\n ).resolves.toEqual({\n kind: \"error\",\n value: \"refresh_token=[redacted] exploded\",\n });\n });\n\n it(\"returns ad group ads rows with paging metadata and nested metrics intact\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n searchMercadoLibreProductAdsAdGroupAdsMock.mockResolvedValueOnce({\n paging: { total: 150, limit: 50, offset: 0 },\n results: buildAdGroupAdsRows(50),\n });\n\n const result = await toolsModule.meliGetProductAdsAdGroupAdsHandler({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n adGroupId: \"ad-group-1\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n metrics: [\"direct_amount\", \"clicks\"],\n channel: \"mshops\",\n campaignId: \"campaign-1\",\n aggregationType: \"daily\",\n limit: 25,\n offset: 0,\n } as never);\n\n expect(result).toEqual({\n kind: \"object\",\n value: expect.objectContaining({\n profile_id: \"profile-1\",\n site_id: \"MLA\",\n ad_group_id: \"ad-group-1\",\n product: \"PADS\",\n mode: \"page\",\n paging: { total: 150, limit: 50, offset: 0 },\n results: buildAdGroupAdsRows(50),\n metadata: expect.objectContaining({\n entity: \"ad_group_ads\",\n fetch_mode: \"page\",\n total: 150,\n requested_limit: 25,\n requested_offset: 0,\n effective_limit: 50,\n effective_offset: 0,\n returned: 50,\n has_more: true,\n next_offset: 50,\n continuation:\n \"Re-run meli_get_product_ads_ad_group_ads with offset=50, limit=50 to continue.\",\n }),\n }),\n });\n\n expect(resolveMercadoLibreProfileOrSelectionMock).toHaveBeenCalledWith(\"profile-1\");\n expect(searchMercadoLibreProductAdsAdGroupAdsMock).toHaveBeenCalledWith(\n \"profile-1\",\n expect.objectContaining({\n siteId: \"MLA\",\n adGroupId: \"ad-group-1\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n metrics: [\"direct_amount\", \"clicks\"],\n channel: \"mshops\",\n campaignId: \"campaign-1\",\n aggregationType: \"daily\",\n limit: 25,\n offset: 0,\n })\n );\n });\n\n it(\"marks an underfilled non-terminal page as not having a reliable continuation\", async () => {\n resolveMercadoLibreProfileOrSelectionMock.mockResolvedValueOnce({\n ok: true,\n value: { profileId: \"profile-1\" },\n });\n searchMercadoLibreProductAdsAdGroupAdsMock.mockResolvedValueOnce({\n paging: { total: 150, limit: 50, offset: 0 },\n results: buildAdGroupAdsRows(1).slice(0, 1),\n });\n\n const result = await toolsModule.meliGetProductAdsAdGroupAdsHandler({\n profileId: \"profile-1\",\n siteId: \"MLA\",\n adGroupId: \"ad-group-1\",\n dateFrom: \"2026-06-01\",\n dateTo: \"2026-06-30\",\n metrics: [\"direct_amount\"],\n limit: 25,\n offset: 0,\n } as never);\n\n expect(result).toEqual({\n kind: \"object\",\n value: expect.objectContaining({\n metadata: expect.objectContaining({\n requested_limit: 25,\n effective_limit: 50,\n returned: 1,\n has_more: false,\n continuation:\n \"The returned page had fewer rows than the effective limit, so page-mode continuation is not reliable for this response.\",\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"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }