@capillarytech/creatives-library 8.0.241 → 8.0.242-alpha.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 (119) hide show
  1. package/package.json +1 -1
  2. package/sagas/__tests__/assetPolling.test.js +607 -0
  3. package/sagas/assetPolling.js +156 -0
  4. package/services/api.js +16 -0
  5. package/services/tests/api.test.js +124 -0
  6. package/translations/en.json +1 -0
  7. package/utils/assetStatusConstants.js +12 -0
  8. package/utils/asyncAssetUpload.js +161 -0
  9. package/utils/tests/asyncAssetUpload.test.js +292 -0
  10. package/utils/transformerUtils.js +42 -0
  11. package/v2Components/CapImageUpload/constants.js +2 -0
  12. package/v2Components/CapImageUpload/index.js +54 -14
  13. package/v2Components/CapImageUpload/index.scss +4 -1
  14. package/v2Components/CapImageUpload/messages.js +4 -0
  15. package/v2Components/CapImageUrlUpload/constants.js +19 -0
  16. package/v2Components/CapImageUrlUpload/index.js +455 -0
  17. package/v2Components/CapImageUrlUpload/index.scss +35 -0
  18. package/v2Components/CapImageUrlUpload/messages.js +47 -0
  19. package/v2Containers/App/constants.js +5 -0
  20. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
  21. package/v2Containers/CreativesContainer/SlideBoxContent.js +57 -2
  22. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -0
  23. package/v2Containers/CreativesContainer/constants.js +2 -0
  24. package/v2Containers/CreativesContainer/index.js +152 -0
  25. package/v2Containers/CreativesContainer/messages.js +4 -0
  26. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  27. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  28. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +25 -0
  29. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +18 -0
  30. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +46 -0
  31. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +4 -0
  32. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +8 -0
  33. package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
  34. package/v2Containers/Templates/_templates.scss +203 -0
  35. package/v2Containers/Templates/actions.js +2 -1
  36. package/v2Containers/Templates/constants.js +1 -0
  37. package/v2Containers/Templates/index.js +273 -30
  38. package/v2Containers/Templates/messages.js +24 -0
  39. package/v2Containers/Templates/reducer.js +2 -0
  40. package/v2Containers/Templates/tests/index.test.js +10 -0
  41. package/v2Containers/TemplatesV2/index.js +3 -2
  42. package/v2Containers/TemplatesV2/messages.js +4 -0
  43. package/v2Containers/WebPush/Create/components/ButtonForm.js +175 -0
  44. package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
  45. package/v2Containers/WebPush/Create/components/ButtonList.js +144 -0
  46. package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
  47. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
  48. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
  49. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
  50. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
  51. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
  52. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +80 -0
  53. package/v2Containers/WebPush/Create/index.js +1755 -0
  54. package/v2Containers/WebPush/Create/index.scss +123 -0
  55. package/v2Containers/WebPush/Create/messages.js +199 -0
  56. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +241 -0
  57. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +290 -0
  58. package/v2Containers/WebPush/Create/preview/PreviewContent.js +81 -0
  59. package/v2Containers/WebPush/Create/preview/PreviewControls.js +240 -0
  60. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
  61. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +144 -0
  62. package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
  63. package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
  64. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  65. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  66. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
  67. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
  68. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
  69. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
  70. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +44 -0
  71. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +110 -0
  72. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
  73. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +72 -0
  74. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +55 -0
  75. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +70 -0
  76. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +512 -0
  77. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +77 -0
  78. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +527 -0
  79. package/v2Containers/WebPush/Create/preview/constants.js +162 -0
  80. package/v2Containers/WebPush/Create/preview/notification-container.scss +104 -0
  81. package/v2Containers/WebPush/Create/preview/preview.scss +409 -0
  82. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +300 -0
  83. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
  84. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
  85. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
  86. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +303 -0
  87. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
  88. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
  89. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
  90. package/v2Containers/WebPush/Create/preview/styles/_base.scss +188 -0
  91. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +106 -0
  92. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
  93. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +75 -0
  94. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +174 -0
  95. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
  96. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1077 -0
  97. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
  98. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
  99. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +128 -0
  100. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +121 -0
  101. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
  102. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +127 -0
  103. package/v2Containers/WebPush/Create/utils/urlValidation.js +116 -0
  104. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
  105. package/v2Containers/WebPush/actions.js +60 -0
  106. package/v2Containers/WebPush/constants.js +108 -0
  107. package/v2Containers/WebPush/index.js +2 -0
  108. package/v2Containers/WebPush/reducer.js +104 -0
  109. package/v2Containers/WebPush/sagas.js +119 -0
  110. package/v2Containers/WebPush/selectors.js +65 -0
  111. package/v2Containers/WebPush/tests/reducer.test.js +863 -0
  112. package/v2Containers/WebPush/tests/sagas.test.js +566 -0
  113. package/v2Containers/WebPush/tests/selectors.test.js +960 -0
  114. package/v2Containers/Whatsapp/constants.js +9 -0
  115. package/v2Containers/Whatsapp/reducer.js +34 -5
  116. package/v2Containers/Whatsapp/sagas.js +61 -10
  117. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +132 -0
  118. package/v2Containers/Whatsapp/tests/reducer.test.js +188 -0
  119. package/v2Containers/Whatsapp/tests/saga.test.js +420 -7
@@ -5,6 +5,11 @@ import {
5
5
  URL_META_TAGS_SUCCESS,
6
6
  URL_META_TAGS_FAILURE,
7
7
  URL_META_TAGS_RESET,
8
+ UPLOAD_WHATSAPP_ASSET_PROCESSING,
9
+ UPLOAD_WHATSAPP_ASSET_COMPLETED,
10
+ UPLOAD_WHATSAPP_ASSET_FAILED,
11
+ UPLOAD_WHATSAPP_ASSET_TIMEOUT,
12
+ CLEAR_WHATSAPP_ASSET,
8
13
  } from "../constants";
9
14
  import { mockData } from "./mockData";
10
15
 
@@ -60,4 +65,187 @@ describe("whatsapp reducer", () => {
60
65
  .set("metaTagsDetails", {});
61
66
  expect(whatsappReducer(state, action)).toEqual(expectedResult);
62
67
  });
68
+
69
+ describe("Async upload processing state management", () => {
70
+ const mockAsset = {
71
+ _id: "asset-123",
72
+ type: "IMAGE",
73
+ url: "https://example.com/image.jpg",
74
+ };
75
+
76
+ it.concurrent("Reducer : UPLOAD_WHATSAPP_ASSET_PROCESSING", () => {
77
+ const state = fromJS({
78
+ uploadedAssetData: {},
79
+ assetProcessing: {},
80
+ uploadAssetSuccess: false,
81
+ assetUploading: false,
82
+ });
83
+ const action = {
84
+ type: UPLOAD_WHATSAPP_ASSET_PROCESSING,
85
+ payload: {
86
+ assetId: "asset-123",
87
+ asset: mockAsset,
88
+ processingStatus: "processing",
89
+ },
90
+ };
91
+ const result = whatsappReducer(state, action);
92
+
93
+ expect(result.get("uploadAssetSuccess")).toBe(false);
94
+ expect(result.get("assetUploading")).toBe(true);
95
+ expect(result.getIn(["assetProcessing", "asset-123", "status"])).toBe("processing");
96
+ expect(result.getIn(["assetProcessing", "asset-123", "asset"])).toEqual(fromJS(mockAsset));
97
+ expect(result.getIn(["assetProcessing", "asset-123", "startTime"])).toBeDefined();
98
+ expect(result.getIn(["assetProcessing", "asset-123", "error"])).toBe(null);
99
+ });
100
+
101
+ it.concurrent("Reducer : UPLOAD_WHATSAPP_ASSET_COMPLETED without templateType", () => {
102
+ const state = fromJS({
103
+ uploadedAssetData: {},
104
+ assetProcessing: {
105
+ "asset-123": {
106
+ status: "processing",
107
+ asset: mockAsset,
108
+ },
109
+ },
110
+ uploadAssetSuccess: false,
111
+ assetUploading: true,
112
+ });
113
+ const action = {
114
+ type: UPLOAD_WHATSAPP_ASSET_COMPLETED,
115
+ payload: {
116
+ assetId: "asset-123",
117
+ asset: mockAsset,
118
+ },
119
+ };
120
+ const result = whatsappReducer(state, action);
121
+
122
+ expect(result.get("uploadAssetSuccess")).toBe(true);
123
+ expect(result.get("assetUploading")).toBe(false);
124
+ expect(result.get("uploadedAssetData")).toEqual(fromJS(mockAsset));
125
+ expect(result.getIn(["assetProcessing", "asset-123", "status"])).toBe("completed");
126
+ expect(result.getIn(["assetProcessing", "asset-123", "asset"])).toEqual(fromJS(mockAsset));
127
+ expect(result.getIn(["assetProcessing", "asset-123", "error"])).toBe(null);
128
+ });
129
+
130
+ it.concurrent("Reducer : UPLOAD_WHATSAPP_ASSET_COMPLETED with templateType", () => {
131
+ const state = fromJS({
132
+ uploadedAssetData: {},
133
+ uploadedAssetData2: {},
134
+ assetProcessing: {
135
+ "asset-123": {
136
+ status: "processing",
137
+ asset: mockAsset,
138
+ },
139
+ },
140
+ uploadAssetSuccess: false,
141
+ assetUploading: true,
142
+ });
143
+ const action = {
144
+ type: UPLOAD_WHATSAPP_ASSET_COMPLETED,
145
+ payload: {
146
+ assetId: "asset-123",
147
+ asset: mockAsset,
148
+ },
149
+ templateType: 2,
150
+ };
151
+ const result = whatsappReducer(state, action);
152
+
153
+ expect(result.get("uploadAssetSuccess")).toBe(true);
154
+ expect(result.get("assetUploading")).toBe(false);
155
+ expect(result.get("uploadedAssetData2")).toEqual(fromJS(mockAsset));
156
+ expect(result.getIn(["assetProcessing", "asset-123", "status"])).toBe("completed");
157
+ });
158
+
159
+ it.concurrent("Reducer : UPLOAD_WHATSAPP_ASSET_FAILED", () => {
160
+ const state = fromJS({
161
+ assetProcessing: {
162
+ "asset-123": {
163
+ status: "processing",
164
+ asset: mockAsset,
165
+ },
166
+ },
167
+ uploadAssetSuccess: false,
168
+ assetUploading: true,
169
+ });
170
+ const action = {
171
+ type: UPLOAD_WHATSAPP_ASSET_FAILED,
172
+ payload: {
173
+ assetId: "asset-123",
174
+ error: "Processing failed",
175
+ },
176
+ };
177
+ const result = whatsappReducer(state, action);
178
+
179
+ expect(result.get("uploadAssetSuccess")).toBe(false);
180
+ expect(result.get("assetUploading")).toBe(false);
181
+ expect(result.getIn(["assetProcessing", "asset-123", "status"])).toBe("failed");
182
+ expect(result.getIn(["assetProcessing", "asset-123", "error"])).toBe("Processing failed");
183
+ });
184
+
185
+ it.concurrent("Reducer : UPLOAD_WHATSAPP_ASSET_TIMEOUT", () => {
186
+ const state = fromJS({
187
+ assetProcessing: {
188
+ "asset-123": {
189
+ status: "processing",
190
+ asset: mockAsset,
191
+ },
192
+ },
193
+ uploadAssetSuccess: false,
194
+ assetUploading: true,
195
+ });
196
+ const action = {
197
+ type: UPLOAD_WHATSAPP_ASSET_TIMEOUT,
198
+ payload: {
199
+ assetId: "asset-123",
200
+ message: "Asset processing is taking longer than expected",
201
+ },
202
+ };
203
+ const result = whatsappReducer(state, action);
204
+
205
+ expect(result.get("uploadAssetSuccess")).toBe(false);
206
+ expect(result.get("assetUploading")).toBe(false);
207
+ expect(result.getIn(["assetProcessing", "asset-123", "status"])).toBe("timeout");
208
+ expect(result.getIn(["assetProcessing", "asset-123", "error"])).toBe("Asset processing is taking longer than expected");
209
+ });
210
+
211
+ it.concurrent("Reducer : CLEAR_WHATSAPP_ASSET without templateType", () => {
212
+ const state = fromJS({
213
+ uploadedAssetData: mockAsset,
214
+ assetProcessing: {
215
+ "asset-123": {
216
+ status: "completed",
217
+ asset: mockAsset,
218
+ },
219
+ },
220
+ });
221
+ const action = {
222
+ type: CLEAR_WHATSAPP_ASSET,
223
+ };
224
+ const result = whatsappReducer(state, action);
225
+
226
+ expect(result.has("uploadedAssetData")).toBe(false);
227
+ expect(result.get("assetProcessing")).toEqual(fromJS({}));
228
+ });
229
+
230
+ it.concurrent("Reducer : CLEAR_WHATSAPP_ASSET with templateType", () => {
231
+ const state = fromJS({
232
+ uploadedAssetData: {},
233
+ uploadedAssetData2: mockAsset,
234
+ assetProcessing: {
235
+ "asset-123": {
236
+ status: "completed",
237
+ asset: mockAsset,
238
+ },
239
+ },
240
+ });
241
+ const action = {
242
+ type: CLEAR_WHATSAPP_ASSET,
243
+ templateType: 2,
244
+ };
245
+ const result = whatsappReducer(state, action);
246
+
247
+ expect(result.has("uploadedAssetData2")).toBe(false);
248
+ expect(result.get("assetProcessing")).toEqual(fromJS({}));
249
+ });
250
+ });
63
251
  });
@@ -1,15 +1,22 @@
1
1
  import { expectSaga, testSaga } from "redux-saga-test-plan";
2
2
  import * as matchers from "redux-saga-test-plan/matchers";
3
- import { takeLatest } from "redux-saga/effects";
4
- import { getMetaTagsDetails, watchGetMetaTagsDetails,v2WhatsappSagas, sendForApprovalCreate } from "../sagas";
3
+ import { takeLatest, call } from "redux-saga/effects";
4
+ import { getMetaTagsDetails, watchGetMetaTagsDetails, v2WhatsappSagas, sendForApprovalCreate, uploadWhatsappAsset } from "../sagas";
5
5
  import {
6
6
  URL_META_TAGS_REQUEST,
7
7
  URL_META_TAGS_SUCCESS,
8
8
  URL_META_TAGS_FAILURE,
9
9
  TEMPLATE_CREATE_SUCCESS,
10
10
  TEMPLATE_CREATE_FAILURE,
11
+ UPLOAD_WHATSAPP_ASSET_REQUEST,
12
+ UPLOAD_WHATSAPP_ASSET_SUCCESS,
13
+ UPLOAD_WHATSAPP_ASSET_FAILURE,
14
+ UPLOAD_WHATSAPP_ASSET_PROCESSING,
15
+ UPLOAD_WHATSAPP_ASSET_FAILED,
11
16
  } from "../constants";
12
17
  import * as Api from "../../../services/api";
18
+ import { pollAssetStatus } from "../../../sagas/assetPolling";
19
+ import { createPollingConfig } from "../../../utils/asyncAssetUpload";
13
20
  import { mockData } from "./mockData";
14
21
 
15
22
  describe("v2WhatsappSagas Combined", () => {
@@ -30,13 +37,13 @@ describe("Whatsapp saga", () => {
30
37
  matchers.call.fn(Api.getMetaTags),
31
38
  {
32
39
  success: true,
33
- result: [],
40
+ response: [],
34
41
  },
35
42
  ],
36
43
  ])
37
44
  .put({
38
45
  type: URL_META_TAGS_SUCCESS,
39
- result: {},
46
+ data: [],
40
47
  })
41
48
  .run();
42
49
  });
@@ -107,13 +114,16 @@ describe("Whatsapp saga", () => {
107
114
  matchers.call.fn(Api.createWhatsappTemplate),
108
115
  {
109
116
  success: true,
110
- result: { response: "Test data"},
117
+ response: "Test data",
118
+ status: { code: 200 },
111
119
  },
112
120
  ],
113
121
  ])
114
122
  .put({
115
123
  type: TEMPLATE_CREATE_SUCCESS,
116
- result: {},
124
+ data: "Test data",
125
+ statusCode: 200,
126
+ errorMsg: undefined,
117
127
  })
118
128
  .run();
119
129
  });
@@ -131,6 +141,7 @@ describe("Whatsapp saga", () => {
131
141
  .put({
132
142
  type: TEMPLATE_CREATE_FAILURE,
133
143
  error: "(#192) Param components[1]['buttons'][0]['phone_number'] is not a valid phone number.",
144
+ errorMsg: "(#192) Param components[1]['buttons'][0]['phone_number'] is not a valid phone number.",
134
145
  })
135
146
  .run();
136
147
  });
@@ -147,7 +158,18 @@ describe("Whatsapp saga", () => {
147
158
  ])
148
159
  .put({
149
160
  type: TEMPLATE_CREATE_FAILURE,
150
- error: 'Invalid params',
161
+ error: {
162
+ name: 'StatusCodeError',
163
+ statusCode: 500,
164
+ message: '500 - Invalid params',
165
+ error: { errorCode: '1004', errorMessage: 'Invalid params' }
166
+ },
167
+ errorMsg: {
168
+ name: 'StatusCodeError',
169
+ statusCode: 500,
170
+ message: '500 - Invalid params',
171
+ error: { errorCode: '1004', errorMessage: 'Invalid params' }
172
+ },
151
173
  })
152
174
  .run();
153
175
  });
@@ -172,6 +194,7 @@ describe("Whatsapp saga", () => {
172
194
  .put({
173
195
  type: TEMPLATE_CREATE_FAILURE,
174
196
  error: 'error',
197
+ errorMsg: 'error',
175
198
  })
176
199
  .run();
177
200
  });
@@ -195,6 +218,7 @@ describe("Whatsapp saga", () => {
195
218
  .put({
196
219
  type: TEMPLATE_CREATE_FAILURE,
197
220
  error: 500,
221
+ errorMsg: 500,
198
222
  })
199
223
  .run();
200
224
  });
@@ -203,6 +227,395 @@ describe("Whatsapp saga", () => {
203
227
  testSaga(sendForApprovalCreate, { callBack }).next().throw(error).next().isDone();
204
228
  });
205
229
  });
230
+
231
+ describe("uploadWhatsappAsset saga", () => {
232
+ const mockAsset = {
233
+ _id: "asset-123",
234
+ type: "IMAGE",
235
+ url: "https://example.com/image.jpg",
236
+ };
237
+
238
+ const mockParams = {
239
+ file: new File(["test"], "test.jpg", { type: "image/jpeg" }),
240
+ assetType: "image",
241
+ fileParams: {},
242
+ templateType: 0,
243
+ whatsappParams: {},
244
+ };
245
+
246
+ it("should handle async upload flow (202 status code)", () => {
247
+ const mockResponse = {
248
+ status: { code: 202 },
249
+ response: {
250
+ assetId: "asset-123",
251
+ asset: mockAsset,
252
+ processingStatus: "processing",
253
+ },
254
+ };
255
+
256
+ return expectSaga(uploadWhatsappAsset, mockParams)
257
+ .provide([
258
+ [matchers.call.fn(Api.uploadFile), mockResponse],
259
+ [
260
+ matchers.call.fn(pollAssetStatus),
261
+ undefined,
262
+ ],
263
+ ])
264
+ .put({
265
+ type: UPLOAD_WHATSAPP_ASSET_PROCESSING,
266
+ payload: {
267
+ assetId: "asset-123",
268
+ asset: mockAsset,
269
+ processingStatus: "processing",
270
+ },
271
+ })
272
+ .call.fn(Api.uploadFile)
273
+ .call.fn(pollAssetStatus)
274
+ .run();
275
+ });
276
+
277
+ it("should handle async upload flow with processingStatus in response", () => {
278
+ const mockResponse = {
279
+ status: { code: 200 },
280
+ response: {
281
+ assetId: "asset-123",
282
+ asset: mockAsset,
283
+ processingStatus: "processing",
284
+ },
285
+ };
286
+
287
+ return expectSaga(uploadWhatsappAsset, mockParams)
288
+ .provide([
289
+ [matchers.call.fn(Api.uploadFile), mockResponse],
290
+ [matchers.call.fn(pollAssetStatus), undefined],
291
+ ])
292
+ .put({
293
+ type: UPLOAD_WHATSAPP_ASSET_PROCESSING,
294
+ payload: {
295
+ assetId: "asset-123",
296
+ asset: mockAsset,
297
+ processingStatus: "processing",
298
+ },
299
+ })
300
+ .call.fn(Api.uploadFile)
301
+ .call.fn(pollAssetStatus)
302
+ .run();
303
+ });
304
+
305
+ it("should handle sync upload flow (201 status code)", () => {
306
+ const mockResponse = {
307
+ status: { code: 201 },
308
+ response: {
309
+ asset: mockAsset,
310
+ },
311
+ };
312
+
313
+ return expectSaga(uploadWhatsappAsset, mockParams)
314
+ .provide([
315
+ [matchers.call.fn(Api.uploadFile), mockResponse],
316
+ ])
317
+ .put({
318
+ type: UPLOAD_WHATSAPP_ASSET_SUCCESS,
319
+ data: mockAsset,
320
+ statusCode: 201,
321
+ templateType: 0,
322
+ })
323
+ .not.call(pollAssetStatus)
324
+ .run();
325
+ });
326
+
327
+ it("should handle sync upload flow (200 status code)", () => {
328
+ const mockResponse = {
329
+ status: { code: 200 },
330
+ response: {
331
+ asset: mockAsset,
332
+ },
333
+ };
334
+
335
+ return expectSaga(uploadWhatsappAsset, mockParams)
336
+ .provide([
337
+ [matchers.call.fn(Api.uploadFile), mockResponse],
338
+ ])
339
+ .put({
340
+ type: UPLOAD_WHATSAPP_ASSET_SUCCESS,
341
+ data: mockAsset,
342
+ statusCode: 200,
343
+ templateType: 0,
344
+ })
345
+ .not.call(pollAssetStatus)
346
+ .run();
347
+ });
348
+
349
+ it("should extract assetId from asset._id when assetId is not present", () => {
350
+ const mockResponse = {
351
+ status: { code: 202 },
352
+ response: {
353
+ asset: { ...mockAsset, _id: "asset-456" },
354
+ processingStatus: "processing",
355
+ },
356
+ };
357
+
358
+ return expectSaga(uploadWhatsappAsset, mockParams)
359
+ .provide([
360
+ [matchers.call.fn(Api.uploadFile), mockResponse],
361
+ [matchers.call.fn(pollAssetStatus), undefined],
362
+ ])
363
+ .put({
364
+ type: UPLOAD_WHATSAPP_ASSET_PROCESSING,
365
+ payload: {
366
+ assetId: "asset-456",
367
+ asset: { ...mockAsset, _id: "asset-456" },
368
+ processingStatus: "processing",
369
+ },
370
+ })
371
+ .call.fn(Api.uploadFile)
372
+ .call.fn(pollAssetStatus)
373
+ .run()
374
+ .then((result) => {
375
+ // Verify the polling config has the correct assetId
376
+ // Find the pollAssetStatus call by checking function name
377
+ const allEffects = result.allEffects || [];
378
+ const pollCall = allEffects.find(effect =>
379
+ effect && effect.CALL && effect.CALL.fn && effect.CALL.fn.name === 'pollAssetStatus'
380
+ );
381
+ expect(pollCall).toBeDefined();
382
+ expect(pollCall.CALL.args[0]).toMatchObject({
383
+ assetId: "asset-456",
384
+ });
385
+ });
386
+ });
387
+
388
+ it("should use assetType from params for polling", () => {
389
+ const videoParams = {
390
+ ...mockParams,
391
+ assetType: "video",
392
+ };
393
+ const mockResponse = {
394
+ status: { code: 202 },
395
+ response: {
396
+ assetId: "asset-123",
397
+ asset: { ...mockAsset, type: "VIDEO" },
398
+ processingStatus: "processing",
399
+ },
400
+ };
401
+
402
+ return expectSaga(uploadWhatsappAsset, videoParams)
403
+ .provide([
404
+ [matchers.call.fn(Api.uploadFile), mockResponse],
405
+ [matchers.call.fn(pollAssetStatus), undefined],
406
+ ])
407
+ .call.fn(Api.uploadFile)
408
+ .call.fn(pollAssetStatus)
409
+ .run()
410
+ .then((result) => {
411
+ const allEffects = result.allEffects || [];
412
+ const pollCall = allEffects.find(effect =>
413
+ effect && effect.CALL && effect.CALL.fn && effect.CALL.fn.name === 'pollAssetStatus'
414
+ );
415
+ expect(pollCall).toBeDefined();
416
+ expect(pollCall.CALL.args[0]).toMatchObject({
417
+ type: "video",
418
+ });
419
+ });
420
+ });
421
+
422
+ it("should fallback to asset.type from response when assetType not in params", () => {
423
+ const paramsWithoutAssetType = {
424
+ ...mockParams,
425
+ assetType: undefined,
426
+ };
427
+ const mockResponse = {
428
+ status: { code: 202 },
429
+ response: {
430
+ assetId: "asset-123",
431
+ asset: { ...mockAsset, type: "VIDEO" },
432
+ processingStatus: "processing",
433
+ },
434
+ };
435
+
436
+ return expectSaga(uploadWhatsappAsset, paramsWithoutAssetType)
437
+ .provide([
438
+ [matchers.call.fn(Api.uploadFile), mockResponse],
439
+ [matchers.call.fn(pollAssetStatus), undefined],
440
+ ])
441
+ .call.fn(Api.uploadFile)
442
+ .call.fn(pollAssetStatus)
443
+ .run()
444
+ .then((result) => {
445
+ const allEffects = result.allEffects || [];
446
+ const pollCall = allEffects.find(effect =>
447
+ effect && effect.CALL && effect.CALL.fn && effect.CALL.fn.name === 'pollAssetStatus'
448
+ );
449
+ expect(pollCall).toBeDefined();
450
+ expect(pollCall.CALL.args[0]).toMatchObject({
451
+ type: "video",
452
+ });
453
+ });
454
+ });
455
+
456
+ it("should default to 'image' when assetType is not available", () => {
457
+ const paramsWithoutAssetType = {
458
+ ...mockParams,
459
+ assetType: undefined,
460
+ };
461
+ const mockResponse = {
462
+ status: { code: 202 },
463
+ response: {
464
+ assetId: "asset-123",
465
+ asset: { ...mockAsset, type: undefined },
466
+ processingStatus: "processing",
467
+ },
468
+ };
469
+
470
+ return expectSaga(uploadWhatsappAsset, paramsWithoutAssetType)
471
+ .provide([
472
+ [matchers.call.fn(Api.uploadFile), mockResponse],
473
+ [matchers.call.fn(pollAssetStatus), undefined],
474
+ ])
475
+ .call.fn(Api.uploadFile)
476
+ .call.fn(pollAssetStatus)
477
+ .run()
478
+ .then((result) => {
479
+ const allEffects = result.allEffects || [];
480
+ const pollCall = allEffects.find(effect =>
481
+ effect && effect.CALL && effect.CALL.fn && effect.CALL.fn.name === 'pollAssetStatus'
482
+ );
483
+ expect(pollCall).toBeDefined();
484
+ expect(pollCall.CALL.args[0]).toMatchObject({
485
+ type: "image",
486
+ });
487
+ });
488
+ });
489
+
490
+ it("should dispatch FAILED action if assetId is missing", () => {
491
+ const mockResponse = {
492
+ status: { code: 202 },
493
+ response: {
494
+ processingStatus: "processing",
495
+ // No assetId or asset._id
496
+ },
497
+ };
498
+
499
+ return expectSaga(uploadWhatsappAsset, mockParams)
500
+ .provide([
501
+ [matchers.call.fn(Api.uploadFile), mockResponse],
502
+ ])
503
+ .put({
504
+ type: UPLOAD_WHATSAPP_ASSET_FAILED,
505
+ payload: {
506
+ assetId: undefined,
507
+ error: 'Asset upload initiated but no asset ID was returned from the server. Unable to track processing status.',
508
+ },
509
+ templateType: 0,
510
+ })
511
+ .not.put({ type: UPLOAD_WHATSAPP_ASSET_PROCESSING })
512
+ .not.call(pollAssetStatus)
513
+ .run();
514
+ });
515
+
516
+ it("should handle error during upload", () => {
517
+ const uploadError = new Error("Upload failed");
518
+
519
+ return expectSaga(uploadWhatsappAsset, mockParams)
520
+ .provide([
521
+ [
522
+ matchers.call.fn(Api.uploadFile),
523
+ Promise.reject(uploadError),
524
+ ],
525
+ ])
526
+ .put({
527
+ type: UPLOAD_WHATSAPP_ASSET_FAILURE,
528
+ error: uploadError,
529
+ })
530
+ .run();
531
+ });
532
+
533
+ it("should pass templateType to polling config", () => {
534
+ const paramsWithTemplateType = {
535
+ ...mockParams,
536
+ templateType: 2,
537
+ };
538
+ const mockResponse = {
539
+ status: { code: 202 },
540
+ response: {
541
+ assetId: "asset-123",
542
+ asset: mockAsset,
543
+ processingStatus: "processing",
544
+ },
545
+ };
546
+
547
+ return expectSaga(uploadWhatsappAsset, paramsWithTemplateType)
548
+ .provide([
549
+ [matchers.call.fn(Api.uploadFile), mockResponse],
550
+ [matchers.call.fn(pollAssetStatus), undefined],
551
+ ])
552
+ .call.fn(Api.uploadFile)
553
+ .call.fn(pollAssetStatus)
554
+ .run()
555
+ .then((result) => {
556
+ // Verify that the polling config includes templateType
557
+ const allEffects = result.allEffects || [];
558
+ const pollCall = allEffects.find(effect =>
559
+ effect && effect.CALL && effect.CALL.fn && effect.CALL.fn.name === 'pollAssetStatus'
560
+ );
561
+ expect(pollCall).toBeDefined();
562
+ const pollingConfig = pollCall.CALL.args[0];
563
+ expect(pollingConfig).toMatchObject({
564
+ onCompleted: expect.any(Function),
565
+ });
566
+ const completedAction = pollingConfig.onCompleted({ assetId: "asset-123", asset: mockAsset });
567
+ expect(completedAction.templateType).toBe(2);
568
+ });
569
+ });
570
+
571
+ it("should handle statusCode from result.statusCode", () => {
572
+ const mockResponse = {
573
+ statusCode: 202,
574
+ response: {
575
+ assetId: "asset-123",
576
+ asset: mockAsset,
577
+ processingStatus: "processing",
578
+ },
579
+ };
580
+
581
+ return expectSaga(uploadWhatsappAsset, mockParams)
582
+ .provide([
583
+ [matchers.call.fn(Api.uploadFile), mockResponse],
584
+ [matchers.call.fn(pollAssetStatus), undefined],
585
+ ])
586
+ .put({
587
+ type: UPLOAD_WHATSAPP_ASSET_PROCESSING,
588
+ payload: {
589
+ assetId: "asset-123",
590
+ asset: mockAsset,
591
+ processingStatus: "processing",
592
+ },
593
+ })
594
+ .call.fn(Api.uploadFile)
595
+ .call.fn(pollAssetStatus)
596
+ .run();
597
+ });
598
+
599
+ it("should handle empty response gracefully", () => {
600
+ const mockResponse = {
601
+ status: { code: 200 },
602
+ response: {},
603
+ };
604
+
605
+ return expectSaga(uploadWhatsappAsset, mockParams)
606
+ .provide([
607
+ [matchers.call.fn(Api.uploadFile), mockResponse],
608
+ ])
609
+ .put({
610
+ type: UPLOAD_WHATSAPP_ASSET_SUCCESS,
611
+ data: undefined,
612
+ statusCode: 200,
613
+ templateType: 0,
614
+ })
615
+ .run();
616
+ });
617
+ });
618
+
206
619
  });
207
620
 
208
621