@congminh1254/shopee-sdk 1.6.2 → 1.8.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 +9 -3
  2. package/lib/__tests__/errors.test.d.ts +1 -0
  3. package/lib/__tests__/errors.test.js +49 -0
  4. package/lib/__tests__/errors.test.js.map +1 -0
  5. package/lib/__tests__/fetch.test.d.ts +1 -0
  6. package/lib/__tests__/fetch.test.js +494 -0
  7. package/lib/__tests__/fetch.test.js.map +1 -0
  8. package/lib/__tests__/integration.test.d.ts +1 -0
  9. package/lib/__tests__/integration.test.js +201 -0
  10. package/lib/__tests__/integration.test.js.map +1 -0
  11. package/lib/__tests__/managers/account-health.manager.test.d.ts +1 -0
  12. package/lib/__tests__/managers/account-health.manager.test.js +446 -0
  13. package/lib/__tests__/managers/account-health.manager.test.js.map +1 -0
  14. package/lib/__tests__/managers/add-on-deal.manager.test.d.ts +1 -0
  15. package/lib/__tests__/managers/add-on-deal.manager.test.js +636 -0
  16. package/lib/__tests__/managers/add-on-deal.manager.test.js.map +1 -0
  17. package/lib/__tests__/managers/ads.manager.test.d.ts +1 -0
  18. package/lib/__tests__/managers/ads.manager.test.js +1016 -0
  19. package/lib/__tests__/managers/ads.manager.test.js.map +1 -0
  20. package/lib/__tests__/managers/ams.manager.test.d.ts +1 -0
  21. package/lib/__tests__/managers/ams.manager.test.js +1170 -0
  22. package/lib/__tests__/managers/ams.manager.test.js.map +1 -0
  23. package/lib/__tests__/managers/auth.manager.test.d.ts +1 -0
  24. package/lib/__tests__/managers/auth.manager.test.js +193 -0
  25. package/lib/__tests__/managers/auth.manager.test.js.map +1 -0
  26. package/lib/__tests__/managers/bundle-deal.manager.test.d.ts +1 -0
  27. package/lib/__tests__/managers/bundle-deal.manager.test.js +465 -0
  28. package/lib/__tests__/managers/bundle-deal.manager.test.js.map +1 -0
  29. package/lib/__tests__/managers/discount.manager.test.d.ts +1 -0
  30. package/lib/__tests__/managers/discount.manager.test.js +615 -0
  31. package/lib/__tests__/managers/discount.manager.test.js.map +1 -0
  32. package/lib/__tests__/managers/fbs.manager.test.d.ts +1 -0
  33. package/lib/__tests__/managers/fbs.manager.test.js +349 -0
  34. package/lib/__tests__/managers/fbs.manager.test.js.map +1 -0
  35. package/lib/__tests__/managers/first-mile.manager.test.d.ts +1 -0
  36. package/lib/__tests__/managers/first-mile.manager.test.js +629 -0
  37. package/lib/__tests__/managers/first-mile.manager.test.js.map +1 -0
  38. package/lib/__tests__/managers/follow-prize.manager.test.d.ts +1 -0
  39. package/lib/__tests__/managers/follow-prize.manager.test.js +408 -0
  40. package/lib/__tests__/managers/follow-prize.manager.test.js.map +1 -0
  41. package/lib/__tests__/managers/global-product.manager.test.d.ts +1 -0
  42. package/lib/__tests__/managers/global-product.manager.test.js +1253 -0
  43. package/lib/__tests__/managers/global-product.manager.test.js.map +1 -0
  44. package/lib/__tests__/managers/livestream.manager.test.d.ts +1 -0
  45. package/lib/__tests__/managers/livestream.manager.test.js +877 -0
  46. package/lib/__tests__/managers/livestream.manager.test.js.map +1 -0
  47. package/lib/__tests__/managers/logistics.manager.test.d.ts +1 -0
  48. package/lib/__tests__/managers/logistics.manager.test.js +1125 -0
  49. package/lib/__tests__/managers/logistics.manager.test.js.map +1 -0
  50. package/lib/__tests__/managers/media-space.manager.test.d.ts +1 -0
  51. package/lib/__tests__/managers/media-space.manager.test.js +556 -0
  52. package/lib/__tests__/managers/media-space.manager.test.js.map +1 -0
  53. package/lib/__tests__/managers/media.manager.test.d.ts +1 -0
  54. package/lib/__tests__/managers/media.manager.test.js +681 -0
  55. package/lib/__tests__/managers/media.manager.test.js.map +1 -0
  56. package/lib/__tests__/managers/merchant.manager.test.d.ts +1 -0
  57. package/lib/__tests__/managers/merchant.manager.test.js +328 -0
  58. package/lib/__tests__/managers/merchant.manager.test.js.map +1 -0
  59. package/lib/__tests__/managers/order.manager.test.d.ts +1 -0
  60. package/lib/__tests__/managers/order.manager.test.js +937 -0
  61. package/lib/__tests__/managers/order.manager.test.js.map +1 -0
  62. package/lib/__tests__/managers/payment.manager.test.d.ts +1 -0
  63. package/lib/__tests__/managers/payment.manager.test.js +1167 -0
  64. package/lib/__tests__/managers/payment.manager.test.js.map +1 -0
  65. package/lib/__tests__/managers/product.manager.test.d.ts +1 -0
  66. package/lib/__tests__/managers/product.manager.test.js +2247 -0
  67. package/lib/__tests__/managers/product.manager.test.js.map +1 -0
  68. package/lib/__tests__/managers/public.manager.test.d.ts +1 -0
  69. package/lib/__tests__/managers/public.manager.test.js +209 -0
  70. package/lib/__tests__/managers/public.manager.test.js.map +1 -0
  71. package/lib/__tests__/managers/push.manager.test.d.ts +1 -0
  72. package/lib/__tests__/managers/push.manager.test.js +244 -0
  73. package/lib/__tests__/managers/push.manager.test.js.map +1 -0
  74. package/lib/__tests__/managers/returns.manager.test.d.ts +1 -0
  75. package/lib/__tests__/managers/returns.manager.test.js +783 -0
  76. package/lib/__tests__/managers/returns.manager.test.js.map +1 -0
  77. package/lib/__tests__/managers/sbs.manager.test.d.ts +1 -0
  78. package/lib/__tests__/managers/sbs.manager.test.js +492 -0
  79. package/lib/__tests__/managers/sbs.manager.test.js.map +1 -0
  80. package/lib/__tests__/managers/shop-category.manager.test.d.ts +1 -0
  81. package/lib/__tests__/managers/shop-category.manager.test.js +425 -0
  82. package/lib/__tests__/managers/shop-category.manager.test.js.map +1 -0
  83. package/lib/__tests__/managers/shop-flash-sale.manager.test.d.ts +1 -0
  84. package/lib/__tests__/managers/shop-flash-sale.manager.test.js +492 -0
  85. package/lib/__tests__/managers/shop-flash-sale.manager.test.js.map +1 -0
  86. package/lib/__tests__/managers/shop.manager.test.d.ts +1 -0
  87. package/lib/__tests__/managers/shop.manager.test.js +637 -0
  88. package/lib/__tests__/managers/shop.manager.test.js.map +1 -0
  89. package/lib/__tests__/managers/top-picks.manager.test.d.ts +1 -0
  90. package/lib/__tests__/managers/top-picks.manager.test.js +324 -0
  91. package/lib/__tests__/managers/top-picks.manager.test.js.map +1 -0
  92. package/lib/__tests__/managers/video.manager.test.d.ts +1 -0
  93. package/lib/__tests__/managers/video.manager.test.js +1039 -0
  94. package/lib/__tests__/managers/video.manager.test.js.map +1 -0
  95. package/lib/__tests__/managers/voucher.manager.test.d.ts +1 -0
  96. package/lib/__tests__/managers/voucher.manager.test.js +442 -0
  97. package/lib/__tests__/managers/voucher.manager.test.js.map +1 -0
  98. package/lib/__tests__/sdk.test.d.ts +1 -0
  99. package/lib/__tests__/sdk.test.js +316 -0
  100. package/lib/__tests__/sdk.test.js.map +1 -0
  101. package/lib/__tests__/storage/custom-token-storage.test.d.ts +1 -0
  102. package/lib/__tests__/storage/custom-token-storage.test.js +337 -0
  103. package/lib/__tests__/storage/custom-token-storage.test.js.map +1 -0
  104. package/lib/__tests__/utils/signature.test.d.ts +1 -0
  105. package/lib/__tests__/utils/signature.test.js +38 -0
  106. package/lib/__tests__/utils/signature.test.js.map +1 -0
  107. package/lib/__tests__/utils/spec-audit.test.d.ts +1 -0
  108. package/lib/__tests__/utils/spec-audit.test.js +176 -0
  109. package/lib/__tests__/utils/spec-audit.test.js.map +1 -0
  110. package/lib/errors.js +2 -0
  111. package/lib/errors.js.map +1 -1
  112. package/lib/fetch.js +1 -1
  113. package/lib/fetch.js.map +1 -1
  114. package/lib/managers/ads.manager.d.ts +1 -41
  115. package/lib/managers/ads.manager.js +0 -54
  116. package/lib/managers/ads.manager.js.map +1 -1
  117. package/lib/managers/auth.manager.d.ts +2 -1
  118. package/lib/managers/auth.manager.js +2 -5
  119. package/lib/managers/auth.manager.js.map +1 -1
  120. package/lib/managers/base.manager.js +1 -0
  121. package/lib/managers/base.manager.js.map +1 -1
  122. package/lib/managers/media.manager.d.ts +36 -1
  123. package/lib/managers/media.manager.js +68 -0
  124. package/lib/managers/media.manager.js.map +1 -1
  125. package/lib/managers/order.manager.js +1 -0
  126. package/lib/managers/order.manager.js.map +1 -1
  127. package/lib/managers/payment.manager.d.ts +13 -1
  128. package/lib/managers/payment.manager.js +26 -0
  129. package/lib/managers/payment.manager.js.map +1 -1
  130. package/lib/managers/product.manager.js +1 -1
  131. package/lib/managers/product.manager.js.map +1 -1
  132. package/lib/schemas/ads.d.ts +2 -56
  133. package/lib/schemas/ams.d.ts +92 -0
  134. package/lib/schemas/base.d.ts +2 -0
  135. package/lib/schemas/discount.d.ts +4 -0
  136. package/lib/schemas/fetch.d.ts +1 -1
  137. package/lib/schemas/livestream.d.ts +14 -0
  138. package/lib/schemas/media.d.ts +107 -0
  139. package/lib/schemas/merchant.d.ts +2 -0
  140. package/lib/schemas/order.d.ts +22 -0
  141. package/lib/schemas/payment.d.ts +96 -0
  142. package/lib/schemas/product.d.ts +253 -15
  143. package/lib/schemas/public.d.ts +3 -0
  144. package/lib/schemas/region.d.ts +9 -2
  145. package/lib/schemas/region.js +9 -2
  146. package/lib/schemas/region.js.map +1 -1
  147. package/lib/schemas/returns.d.ts +38 -1
  148. package/lib/schemas/shop.d.ts +16 -0
  149. package/lib/schemas/video.d.ts +83 -37
  150. package/lib/schemas/voucher.d.ts +2 -0
  151. package/lib/sdk.d.ts +6 -1
  152. package/lib/sdk.js +53 -6
  153. package/lib/sdk.js.map +1 -1
  154. package/lib/storage/custom-token-storage.js +2 -0
  155. package/lib/storage/custom-token-storage.js.map +1 -1
  156. package/lib/version.d.ts +1 -1
  157. package/lib/version.js +1 -1
  158. package/package.json +3 -3
package/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  🎉 **The most complete, production-ready TypeScript SDK for Shopee Open API** 🎉
10
10
 
11
+ <a href="https://www.buymeacoffee.com/congminh1254" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me a Coffee" style="height: 40px !important;width: 150px !important;" ></a>
12
+
11
13
  Build powerful Shopee integrations with confidence using our fully-featured SDK that covers **100% of Shopee's API endpoints**. Trusted by developers, built by the community.
12
14
 
13
15
  ## 📚 Documentation
@@ -32,6 +34,7 @@ Build powerful Shopee integrations with confidence using our fully-featured SDK
32
34
  - [PushManager](./docs/managers/push.md) - Webhooks and notifications
33
35
  - [PublicManager](./docs/managers/public.md) - Public API endpoints
34
36
  - [AdsManager](./docs/managers/ads.md) - Advertising campaigns
37
+ - [AmsManager](./docs/managers/ams.md) - Affiliate Marketing Solutions (AMS)
35
38
  - [AccountHealthManager](./docs/managers/account-health.md) - Performance metrics
36
39
  - [ShopManager](./docs/managers/shop.md) - Shop information and settings
37
40
  - [MediaManager](./docs/managers/media.md) - Image and video upload
@@ -50,6 +53,7 @@ Build powerful Shopee integrations with confidence using our fully-featured SDK
50
53
  - [SbsManager](./docs/managers/sbs.md) - Shopee Business Services (SBS) warehouse inventory
51
54
  - [FbsManager](./docs/managers/fbs.md) - Fulfillment by Shopee operations
52
55
  - [LivestreamManager](./docs/managers/livestream.md) - Live streaming features
56
+ - [VideoManager](./docs/managers/video.md) - Shopee Video features and analytics
53
57
 
54
58
  ## Installation
55
59
 
@@ -60,7 +64,7 @@ npm install @congminh1254/shopee-sdk
60
64
  **Requirements:** Node.js >= 20.0.0
61
65
 
62
66
  ### What You Get
63
- ✅ Complete TypeScript definitions for all 27 API managers
67
+ ✅ Complete TypeScript definitions for all 29 API managers
64
68
  ✅ Automatic token refresh and management
65
69
  ✅ Built-in error handling and retry logic
66
70
  ✅ Zero dependencies (except node-fetch)
@@ -124,12 +128,12 @@ See the [Setup Guide](./docs/guides/setup.md) and [Authentication Guide](./docs/
124
128
  ## Why Choose This SDK?
125
129
 
126
130
  ### 🚀 Production-Ready & Battle-Tested
127
- - **75%+ test coverage** with 86 comprehensive tests - ensuring reliability in production
131
+ - **95%+ test coverage** with 671 comprehensive tests - ensuring reliability in production
128
132
  - **Zero compromises** - Every Shopee API endpoint is implemented and documented
129
133
  - **Type-safe** - Full TypeScript definitions prevent errors before they happen
130
134
  - **Actively maintained** - Regular updates to stay in sync with Shopee API changes
131
135
 
132
- ### 💪 Complete API Coverage - All 27 Managers Implemented
136
+ ### 💪 Complete API Coverage - All 29 Managers Implemented
133
137
  Unlike other SDKs with partial coverage, we provide **complete access** to every Shopee API:
134
138
 
135
139
  **Core Commerce:**
@@ -146,6 +150,7 @@ Unlike other SDKs with partial coverage, we provide **complete access** to every
146
150
  - ⚡ **ShopFlashSaleManager** - Flash sale campaigns
147
151
  - 🎯 **FollowPrizeManager** - Follow prize activities
148
152
  - ⭐ **TopPicksManager** - Top picks product collections
153
+ - 📣 **AmsManager** - Affiliate marketing solutions (AMS)
149
154
 
150
155
  **Store Management:**
151
156
  - 🏪 **ShopManager** - Shop information and profile management
@@ -165,6 +170,7 @@ Unlike other SDKs with partial coverage, we provide **complete access** to every
165
170
  - 🏭 **SbsManager** - Shopee Business Services warehouse inventory
166
171
  - 📦 **FbsManager** - Fulfillment by Shopee operations
167
172
  - 📹 **LivestreamManager** - Live streaming features
173
+ - 🎬 **VideoManager** - Shopee Video features and analytics
168
174
 
169
175
  ### ✨ Developer Experience First
170
176
  - **Intuitive API design** - Clean, consistent interfaces across all managers
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { ShopeeSdkError, ShopeeApiError } from "../errors.js";
2
+ describe("ShopeeSdkError", () => {
3
+ it("should create an error with the correct name and message", () => {
4
+ const errorMessage = "Test error message";
5
+ const error = new ShopeeSdkError(errorMessage);
6
+ expect(error).toBeInstanceOf(Error);
7
+ expect(error).toBeInstanceOf(ShopeeSdkError);
8
+ expect(error.name).toBe("ShopeeSdkError");
9
+ expect(error.message).toBe(errorMessage);
10
+ });
11
+ it("should have a proper error stack", () => {
12
+ const error = new ShopeeSdkError("Test error");
13
+ expect(error.stack).toBeDefined();
14
+ expect(typeof error.stack).toBe("string");
15
+ });
16
+ });
17
+ describe("ShopeeApiError", () => {
18
+ it("should create an error with status and data", () => {
19
+ const status = 400;
20
+ const data = { error: "Invalid request", message: "Bad request" };
21
+ const error = new ShopeeApiError(status, data);
22
+ expect(error).toBeInstanceOf(Error);
23
+ expect(error).toBeInstanceOf(ShopeeApiError);
24
+ expect(error.name).toBe("ShopeeApiError");
25
+ expect(error.status).toBe(status);
26
+ expect(error.data).toBe(data);
27
+ expect(error.message).toContain(status.toString());
28
+ expect(error.message).toContain(JSON.stringify(data));
29
+ });
30
+ it("should handle different types of data", () => {
31
+ const testCases = [
32
+ { status: 404, data: { error: "Not found" } },
33
+ { status: 500, data: "Internal server error" },
34
+ { status: 401, data: null },
35
+ { status: 403, data: undefined },
36
+ ];
37
+ testCases.forEach(({ status, data }) => {
38
+ const error = new ShopeeApiError(status, data);
39
+ expect(error.status).toBe(status);
40
+ expect(error.data).toBe(data);
41
+ });
42
+ });
43
+ it("should have a proper error stack", () => {
44
+ const error = new ShopeeApiError(500, { error: "Test error" });
45
+ expect(error.stack).toBeDefined();
46
+ expect(typeof error.stack).toBe("string");
47
+ });
48
+ });
49
+ //# sourceMappingURL=errors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.test.js","sourceRoot":"","sources":["../../src/__tests__/errors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,494 @@
1
+ import { jest, describe, beforeEach, it, expect } from "@jest/globals";
2
+ import { ShopeeRegion } from "../schemas/region.js";
3
+ import { ShopeeSdkError } from "../errors.js";
4
+ // Mock fetch function
5
+ const mockFetch = jest.fn();
6
+ // Mock the entire fetch module before importing ShopeeFetch
7
+ jest.unstable_mockModule("node-fetch", () => ({
8
+ default: mockFetch,
9
+ Blob: globalThis.Blob,
10
+ FormData: globalThis.FormData,
11
+ Headers: globalThis.Headers,
12
+ }));
13
+ // Import ShopeeFetch after mocking
14
+ const { ShopeeFetch } = await import("../fetch.js");
15
+ function getRequestContentType(url, options) {
16
+ return new Request(url, options).headers.get("content-type");
17
+ }
18
+ describe("ShopeeFetch", () => {
19
+ let mockConfig;
20
+ let mockSdk;
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ mockSdk = {
24
+ getAuthToken: jest.fn(),
25
+ refreshToken: jest.fn(),
26
+ };
27
+ mockConfig = {
28
+ partner_id: 12345,
29
+ partner_key: "test_partner_key",
30
+ shop_id: 67890,
31
+ region: ShopeeRegion.GLOBAL,
32
+ base_url: "https://partner.test-stable.shopeemobile.com/api/v2",
33
+ sdk: mockSdk,
34
+ };
35
+ });
36
+ describe("fetch method", () => {
37
+ it("should make successful GET request without auth", async () => {
38
+ const mockResponse = {
39
+ request_id: "test-request-id",
40
+ error: "",
41
+ message: "",
42
+ response: { data: "test data" },
43
+ };
44
+ mockFetch.mockResolvedValueOnce({
45
+ status: 200,
46
+ headers: new Map([["content-type", "application/json"]]),
47
+ json: jest.fn(() => Promise.resolve(mockResponse)),
48
+ });
49
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint");
50
+ expect(mockFetch).toHaveBeenCalledTimes(1);
51
+ expect(result).toEqual(mockResponse);
52
+ });
53
+ it("should make successful POST request with body", async () => {
54
+ const mockResponse = { success: true };
55
+ const requestBody = { key: "value" };
56
+ mockFetch.mockResolvedValueOnce({
57
+ status: 200,
58
+ headers: new Map([["content-type", "application/json"]]),
59
+ json: jest.fn(() => Promise.resolve(mockResponse)),
60
+ });
61
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
62
+ method: "POST",
63
+ body: requestBody,
64
+ });
65
+ expect(mockFetch).toHaveBeenCalledTimes(1);
66
+ const [, options] = mockFetch.mock.calls[0];
67
+ expect(options).toBeDefined();
68
+ expect(options.method).toBe("POST");
69
+ expect(options.body).toBe(JSON.stringify(requestBody));
70
+ expect(result).toEqual(mockResponse);
71
+ });
72
+ it("should serialize multipart bodies when the payload contains binary fields", async () => {
73
+ const mockResponse = { success: true };
74
+ mockFetch.mockResolvedValue({
75
+ status: 200,
76
+ headers: new Map([["content-type", "application/json"]]),
77
+ json: jest.fn(() => Promise.resolve(mockResponse)),
78
+ });
79
+ const image = Buffer.from("image-data");
80
+ await ShopeeFetch.fetch(mockConfig, "/media_space/upload_image", {
81
+ method: "POST",
82
+ body: {
83
+ scene: "normal",
84
+ image,
85
+ },
86
+ });
87
+ const [, options] = mockFetch.mock.calls[0];
88
+ expect(options).toBeDefined();
89
+ const formData = options.body;
90
+ expect(formData).toBeInstanceOf(FormData);
91
+ expect(formData.get("scene")).toBe("normal");
92
+ expect(formData.get("image")).toBeInstanceOf(Blob);
93
+ expect(options.headers.get("content-type")).toBeNull();
94
+ expect(getRequestContentType(`${mockConfig.base_url}/media_space/upload_image`, options)).toMatch(/^multipart\/form-data; boundary=/);
95
+ });
96
+ it("should serialize multipart bodies when the payload contains binary arrays", async () => {
97
+ const mockResponse = { success: true };
98
+ mockFetch.mockResolvedValue({
99
+ status: 200,
100
+ headers: new Map([["content-type", "application/json"]]),
101
+ json: jest.fn(() => Promise.resolve(mockResponse)),
102
+ });
103
+ const image1 = Buffer.from("image-1");
104
+ const image2 = Buffer.from("image-2");
105
+ await ShopeeFetch.fetch(mockConfig, "/media/upload_image", {
106
+ method: "POST",
107
+ body: {
108
+ business: 2,
109
+ scene: 1,
110
+ images: [image1, image2],
111
+ },
112
+ });
113
+ const [, options] = mockFetch.mock.calls[0];
114
+ expect(options).toBeDefined();
115
+ const formData = options.body;
116
+ const images = formData.getAll("images");
117
+ expect(formData).toBeInstanceOf(FormData);
118
+ expect(formData.get("business")).toBe("2");
119
+ expect(formData.get("scene")).toBe("1");
120
+ expect(images).toHaveLength(2);
121
+ images.forEach((image) => expect(image).toBeInstanceOf(Blob));
122
+ expect(options.headers.get("content-type")).toBeNull();
123
+ expect(getRequestContentType(`${mockConfig.base_url}/media/upload_image`, options)).toMatch(/^multipart\/form-data; boundary=/);
124
+ });
125
+ it("should pass through FormData bodies without forcing JSON content type", async () => {
126
+ const mockResponse = { success: true };
127
+ mockFetch.mockResolvedValue({
128
+ status: 200,
129
+ headers: new Map([["content-type", "application/json"]]),
130
+ json: jest.fn(() => Promise.resolve(mockResponse)),
131
+ });
132
+ const formData = new FormData();
133
+ formData.append("video_upload_id", "upload-id");
134
+ formData.append("part_content", new Blob([Buffer.from("video-part")]), "part.bin");
135
+ await ShopeeFetch.fetch(mockConfig, "/media_space/upload_video_part", {
136
+ method: "POST",
137
+ body: formData,
138
+ });
139
+ const [, options] = mockFetch.mock.calls[0];
140
+ expect(options).toBeDefined();
141
+ expect(options.body).toBe(formData);
142
+ expect(options.headers.get("content-type")).toBeNull();
143
+ expect(getRequestContentType(`${mockConfig.base_url}/media_space/upload_video_part`, options)).toMatch(/^multipart\/form-data; boundary=/);
144
+ });
145
+ it("should serialize multipart bodies with null, undefined, named/unnamed Blobs, and nested objects", async () => {
146
+ const mockResponse = { success: true };
147
+ mockFetch.mockResolvedValue({
148
+ status: 200,
149
+ headers: new Map([["content-type", "application/json"]]),
150
+ json: jest.fn(() => Promise.resolve(mockResponse)),
151
+ });
152
+ const fileWithFilename = new Blob([Buffer.from("file-1")]);
153
+ Object.defineProperty(fileWithFilename, "name", {
154
+ value: "custom-file.txt",
155
+ writable: false,
156
+ });
157
+ const fileWithoutFilename = new Blob([Buffer.from("file-2")]);
158
+ await ShopeeFetch.fetch(mockConfig, "/media_space/upload_image", {
159
+ method: "POST",
160
+ body: {
161
+ image: Buffer.from("image-data"),
162
+ nullField: null,
163
+ undefinedField: undefined,
164
+ arrayField: ["string", null, undefined],
165
+ namedBlob: fileWithFilename,
166
+ unnamedBlob: fileWithoutFilename,
167
+ nestedObject: { key: "nested" },
168
+ },
169
+ });
170
+ const [, options] = mockFetch.mock.calls[0];
171
+ expect(options).toBeDefined();
172
+ const formData = options.body;
173
+ expect(formData).toBeInstanceOf(FormData);
174
+ expect(formData.get("image")).toBeInstanceOf(Blob);
175
+ expect(formData.get("nullField")).toBeNull();
176
+ expect(formData.get("undefinedField")).toBeNull();
177
+ expect(formData.getAll("arrayField")).toEqual(["string"]);
178
+ expect(formData.get("namedBlob")).toBeInstanceOf(Blob);
179
+ expect(formData.get("unnamedBlob")).toBeInstanceOf(Blob);
180
+ expect(formData.get("nestedObject")).toBe(JSON.stringify({ key: "nested" }));
181
+ });
182
+ it("should make authenticated request with valid token", async () => {
183
+ const mockToken = {
184
+ access_token: "test_access_token",
185
+ refresh_token: "test_refresh_token",
186
+ expire_in: 3600,
187
+ expired_at: Date.now() + 3600000,
188
+ shop_id: 67890,
189
+ request_id: "test-request-id",
190
+ error: "",
191
+ message: "",
192
+ };
193
+ const mockResponse = { data: "authenticated data" };
194
+ mockSdk.getAuthToken.mockResolvedValue(mockToken);
195
+ mockFetch.mockResolvedValueOnce({
196
+ status: 200,
197
+ headers: new Map([["content-type", "application/json"]]),
198
+ json: jest.fn(() => Promise.resolve(mockResponse)),
199
+ });
200
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
201
+ auth: true,
202
+ });
203
+ expect(mockSdk.getAuthToken).toHaveBeenCalledTimes(1);
204
+ expect(mockFetch).toHaveBeenCalledTimes(1);
205
+ const [url] = mockFetch.mock.calls[0];
206
+ expect(url).toContain("access_token=test_access_token");
207
+ expect(url).toContain("shop_id=67890");
208
+ expect(result).toEqual(mockResponse);
209
+ });
210
+ it("should refresh token when expired and retry request", async () => {
211
+ const expiredToken = {
212
+ access_token: "expired_token",
213
+ refresh_token: "test_refresh_token",
214
+ expire_in: 3600,
215
+ expired_at: Date.now() - 1000, // Expired
216
+ shop_id: 67890,
217
+ request_id: "test-request-id",
218
+ error: "",
219
+ message: "",
220
+ };
221
+ const newToken = {
222
+ access_token: "new_access_token",
223
+ refresh_token: "new_refresh_token",
224
+ expire_in: 3600,
225
+ expired_at: Date.now() + 3600000,
226
+ shop_id: 67890,
227
+ request_id: "test-request-id",
228
+ error: "",
229
+ message: "",
230
+ };
231
+ const mockResponse = { data: "authenticated data" };
232
+ mockSdk.getAuthToken.mockResolvedValue(expiredToken);
233
+ mockSdk.refreshToken.mockResolvedValue(newToken);
234
+ mockFetch.mockResolvedValueOnce({
235
+ status: 200,
236
+ headers: new Map([["content-type", "application/json"]]),
237
+ json: jest.fn(() => Promise.resolve(mockResponse)),
238
+ });
239
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
240
+ auth: true,
241
+ });
242
+ expect(mockSdk.getAuthToken).toHaveBeenCalledTimes(1);
243
+ expect(mockSdk.refreshToken).toHaveBeenCalledTimes(1);
244
+ expect(result).toEqual(mockResponse);
245
+ });
246
+ it("should throw error when no access token found", async () => {
247
+ mockSdk.getAuthToken.mockResolvedValue(null);
248
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint", { auth: true })).rejects.toThrow(ShopeeSdkError);
249
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint", { auth: true })).rejects.toThrow("No access token found");
250
+ });
251
+ it("should handle invalid access token error and retry after refresh", async () => {
252
+ const invalidTokenResponse = {
253
+ error: "invalid_acceess_token",
254
+ message: "Invalid access token",
255
+ request_id: "test-request-id",
256
+ };
257
+ const successResponse = { data: "success after refresh" };
258
+ const mockToken = {
259
+ access_token: "old_token",
260
+ refresh_token: "refresh_token",
261
+ expire_in: 3600,
262
+ expired_at: Date.now() + 3600000,
263
+ shop_id: 67890,
264
+ request_id: "test-request-id",
265
+ error: "",
266
+ message: "",
267
+ };
268
+ mockSdk.getAuthToken.mockResolvedValue(mockToken);
269
+ mockSdk.refreshToken.mockResolvedValue(mockToken);
270
+ // First call returns invalid token error, second call succeeds
271
+ mockFetch
272
+ .mockResolvedValueOnce({
273
+ status: 401,
274
+ headers: new Map([["content-type", "application/json"]]),
275
+ json: jest.fn(() => Promise.resolve(invalidTokenResponse)),
276
+ })
277
+ .mockResolvedValueOnce({
278
+ status: 200,
279
+ headers: new Map([["content-type", "application/json"]]),
280
+ json: jest.fn(() => Promise.resolve(successResponse)),
281
+ });
282
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
283
+ auth: true,
284
+ });
285
+ expect(mockSdk.refreshToken).toHaveBeenCalledTimes(1);
286
+ expect(mockFetch).toHaveBeenCalledTimes(2);
287
+ expect(result).toEqual(successResponse);
288
+ });
289
+ it("should include custom headers in request", async () => {
290
+ const mockResponse = { data: "test" };
291
+ const customHeaders = {
292
+ "X-Custom-Header": "custom-value",
293
+ };
294
+ mockFetch.mockResolvedValueOnce({
295
+ status: 200,
296
+ headers: new Map([["content-type", "application/json"]]),
297
+ json: jest.fn(() => Promise.resolve(mockResponse)),
298
+ });
299
+ await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
300
+ headers: customHeaders,
301
+ });
302
+ expect(mockFetch).toHaveBeenCalledTimes(1);
303
+ const [, options] = mockFetch.mock.calls[0];
304
+ expect(options).toBeDefined();
305
+ expect(options.headers.get("X-Custom-Header")).toBe("custom-value");
306
+ });
307
+ it("should include User-Agent header with SDK version", async () => {
308
+ const mockResponse = { data: "test" };
309
+ mockFetch.mockResolvedValueOnce({
310
+ status: 200,
311
+ headers: new Map([["content-type", "application/json"]]),
312
+ json: jest.fn(() => Promise.resolve(mockResponse)),
313
+ });
314
+ await ShopeeFetch.fetch(mockConfig, "/test/endpoint");
315
+ expect(mockFetch).toHaveBeenCalledTimes(1);
316
+ const [, options] = mockFetch.mock.calls[0];
317
+ expect(options).toBeDefined();
318
+ const userAgent = options.headers.get("user-agent");
319
+ expect(userAgent).toMatch(/^congminh1254\/shopee-sdk\/v\d+\.\d+\.\d+$/);
320
+ });
321
+ it("should include query parameters in URL", async () => {
322
+ const mockResponse = { data: "test" };
323
+ const params = {
324
+ param1: "value1",
325
+ param2: 123,
326
+ param3: ["array1", "array2"],
327
+ };
328
+ mockFetch.mockResolvedValueOnce({
329
+ status: 200,
330
+ headers: new Map([["content-type", "application/json"]]),
331
+ json: jest.fn(() => Promise.resolve(mockResponse)),
332
+ });
333
+ await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
334
+ params,
335
+ });
336
+ expect(mockFetch).toHaveBeenCalledTimes(1);
337
+ const [url] = mockFetch.mock.calls[0];
338
+ expect(url).toContain("param1=value1");
339
+ expect(url).toContain("param2=123");
340
+ expect(url).toContain("param3=array1");
341
+ expect(url).toContain("param3=array2");
342
+ });
343
+ it("should remove undefined query parameters from URL", async () => {
344
+ const mockResponse = { data: "test" };
345
+ const params = {
346
+ param1: "value1",
347
+ param2: undefined,
348
+ };
349
+ mockFetch.mockResolvedValueOnce({
350
+ status: 200,
351
+ headers: new Map([["content-type", "application/json"]]),
352
+ json: jest.fn(() => Promise.resolve(mockResponse)),
353
+ });
354
+ await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
355
+ params,
356
+ });
357
+ expect(mockFetch).toHaveBeenCalledTimes(1);
358
+ const [url] = mockFetch.mock.calls[0];
359
+ expect(url).toContain("param1=value1");
360
+ expect(url).not.toContain("param2");
361
+ });
362
+ it("should handle invalid access token error when refresh fails", async () => {
363
+ const invalidTokenResponse = {
364
+ error: "invalid_acceess_token",
365
+ message: "Invalid access token",
366
+ request_id: "test-request-id",
367
+ };
368
+ const mockToken = {
369
+ access_token: "old_token",
370
+ refresh_token: "refresh_token",
371
+ expire_in: 3600,
372
+ expired_at: Date.now() + 3600000,
373
+ shop_id: 67890,
374
+ request_id: "test-request-id",
375
+ error: "",
376
+ message: "",
377
+ };
378
+ mockSdk.getAuthToken.mockResolvedValue(mockToken);
379
+ mockSdk.refreshToken.mockRejectedValue(new Error("Refresh failed"));
380
+ mockFetch.mockResolvedValueOnce({
381
+ status: 401,
382
+ headers: new Map([["content-type", "application/json"]]),
383
+ json: jest.fn(() => Promise.resolve(invalidTokenResponse)),
384
+ });
385
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint", { auth: true })).rejects.toThrow("API Error: 401");
386
+ });
387
+ it("should throw ShopeeApiError when API returns error", async () => {
388
+ const errorResponse = {
389
+ error: "error_code",
390
+ message: "Error message",
391
+ request_id: "test-request-id",
392
+ };
393
+ mockFetch.mockResolvedValueOnce({
394
+ status: 400,
395
+ headers: new Map([["content-type", "application/json"]]),
396
+ json: jest.fn(() => Promise.resolve(errorResponse)),
397
+ });
398
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint")).rejects.toThrow("API Error: 400");
399
+ });
400
+ it("should throw error for unknown response type", async () => {
401
+ const headers = new Map([["content-type", "text/html"]]);
402
+ mockFetch.mockResolvedValueOnce({
403
+ status: 200,
404
+ headers: {
405
+ get: (name) => headers.get(name.toLowerCase()),
406
+ },
407
+ text: jest.fn(() => Promise.resolve("<html>Not JSON</html>")),
408
+ json: jest.fn(),
409
+ });
410
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint")).rejects.toThrow("Unknown response type");
411
+ });
412
+ it("should return Buffer for application/pdf response", async () => {
413
+ const text = "%PDF-1.4 test pdf content";
414
+ const uint8 = new TextEncoder().encode(text);
415
+ const headers = new Map([["content-type", "application/pdf"]]);
416
+ mockFetch.mockResolvedValueOnce({
417
+ status: 200,
418
+ headers: {
419
+ get: (name) => headers.get(name.toLowerCase()),
420
+ },
421
+ arrayBuffer: jest.fn(() => Promise.resolve(uint8.buffer)),
422
+ });
423
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint");
424
+ expect(result).toBeInstanceOf(Buffer);
425
+ expect(result.toString()).toBe(text);
426
+ });
427
+ it("should return Buffer for application/octet-stream response", async () => {
428
+ const text = "binary data";
429
+ const uint8 = new TextEncoder().encode(text);
430
+ const headers = new Map([["content-type", "application/octet-stream"]]);
431
+ mockFetch.mockResolvedValueOnce({
432
+ status: 200,
433
+ headers: {
434
+ get: (name) => headers.get(name.toLowerCase()),
435
+ },
436
+ arrayBuffer: jest.fn(() => Promise.resolve(uint8.buffer)),
437
+ });
438
+ const result = await ShopeeFetch.fetch(mockConfig, "/test/endpoint");
439
+ expect(result).toBeInstanceOf(Buffer);
440
+ expect(result.toString()).toBe(text);
441
+ });
442
+ it("should handle network errors", async () => {
443
+ const networkError = new Error("Network failed");
444
+ networkError.name = "FetchError";
445
+ mockFetch.mockRejectedValueOnce(networkError);
446
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint")).rejects.toThrow("Network error: Network failed");
447
+ });
448
+ it("should handle unexpected errors", async () => {
449
+ const unexpectedError = new Error("Unexpected error");
450
+ mockFetch.mockRejectedValueOnce(unexpectedError);
451
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint")).rejects.toThrow("Unexpected error: Unexpected error");
452
+ });
453
+ it("should handle unknown non-Error exceptions", async () => {
454
+ mockFetch.mockRejectedValueOnce("string error");
455
+ await expect(ShopeeFetch.fetch(mockConfig, "/test/endpoint")).rejects.toThrow("Unknown error occurred");
456
+ });
457
+ it("should handle null body correctly", async () => {
458
+ const mockResponse = { success: true };
459
+ mockFetch.mockResolvedValueOnce({
460
+ status: 200,
461
+ headers: new Map([["content-type", "application/json"]]),
462
+ json: jest.fn(() => Promise.resolve(mockResponse)),
463
+ });
464
+ await ShopeeFetch.fetch(mockConfig, "/test/endpoint", {
465
+ method: "POST",
466
+ body: null,
467
+ });
468
+ const [, options] = mockFetch.mock.calls[0];
469
+ expect(options).toBeDefined();
470
+ expect(options.body).toBe("null");
471
+ });
472
+ it("should serialize multipart bodies with fallback isBlobLike conditions", async () => {
473
+ const mockResponse = { success: true };
474
+ mockFetch.mockResolvedValueOnce({
475
+ status: 200,
476
+ headers: new Map([["content-type", "application/json"]]),
477
+ json: jest.fn(() => Promise.resolve(mockResponse)),
478
+ });
479
+ await ShopeeFetch.fetch(mockConfig, "/media_space/upload_image", {
480
+ method: "POST",
481
+ body: {
482
+ image: Buffer.from("image-data"),
483
+ nullProto: Object.create(null),
484
+ },
485
+ });
486
+ const [, options] = mockFetch.mock.calls[0];
487
+ expect(options).toBeDefined();
488
+ const formData = options.body;
489
+ expect(formData).toBeInstanceOf(FormData);
490
+ expect(formData.get("image")).toBeInstanceOf(Blob);
491
+ });
492
+ });
493
+ });
494
+ //# sourceMappingURL=fetch.test.js.map