@yrpri/api 9.0.220 → 9.0.222

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 (55) hide show
  1. package/agents/managers/subscriptionManager.js +2 -2
  2. package/app.js +11 -8
  3. package/controllers/communities.cjs +3 -1
  4. package/controllers/groups.cjs +1 -1
  5. package/controllers/images.cjs +12 -1
  6. package/models/domain.cjs +1 -1
  7. package/models/image.cjs +90 -24
  8. package/package.json +83 -63
  9. package/scripts/cloning/clearUsersForCommunitiesFromUrl.js +1 -1
  10. package/scripts/cloning/cloneFromUrlScript.js +1 -1
  11. package/scripts/cloning/cloneWBFromUrlScriptAndCreateLinks.js +1 -1
  12. package/scripts/cloning/cloneWBFromUrlScriptNoUsersOrPoints.js +1 -1
  13. package/scripts/cloning/cloneWBSerbianFromUrlScriptAndCreateLinks.js +1 -1
  14. package/scripts/cloning/copyCommunityConfigAndTranslationsFromURL.js +1 -1
  15. package/scripts/cloning/copyGroupConfigAndTranslationsFromURL.js +1 -1
  16. package/scripts/cloning/copyPostVideosFromURL.js +1 -1
  17. package/scripts/cloning/deepCloneSerbianWBFromUrlScriptAndCreateLinks.js +1 -1
  18. package/scripts/cloning/deepCloneWBFromUrlScriptAndCreateLinks.js +1 -1
  19. package/scripts/cloning/setAdminsFromURL.js +1 -1
  20. package/scripts/cloning/setExternalIdsFromURL.js +1 -1
  21. package/scripts/endorsementFraudDetection/bulkDeleteDuplicateEndorsmentsFromUrl.js +1 -1
  22. package/scripts/landUseGame/export3Ddata.js +1 -1
  23. package/scripts/movePostsToGroupsRecountGroupFromUrl.js +1 -1
  24. package/scripts/recountALLCommunityGroupCounts.js +1 -1
  25. package/scripts/recountCommunitesFromUrl.js +1 -1
  26. package/scripts/recountCommunity.js +1 -1
  27. package/scripts/setLanguageOnGroupCommunitesFromUrl.js +1 -1
  28. package/services/engine/allOurIdeas/aiHelper.d.ts +1 -2
  29. package/services/engine/analytics/manager.cjs +1 -1
  30. package/services/engine/analytics/plausible/manager.cjs +1 -1
  31. package/services/engine/analytics/utils.cjs +1 -1
  32. package/services/engine/notifications/emails_utils.cjs +10 -1
  33. package/services/engine/recommendations/events_manager.cjs +1 -1
  34. package/services/engine/reports/common_utils.cjs +1 -1
  35. package/services/llms/baseChatBot.d.ts +1 -2
  36. package/services/llms/imageGeneration/s3Service.js +72 -11
  37. package/services/scripts/translation_replace_text_from_url.js +1 -1
  38. package/services/utils/redisConnection.cjs +2 -3
  39. package/services/utils/translation_cloning.cjs +1 -1
  40. package/services/utils/translation_helpers.cjs +1 -1
  41. package/tests/emails_utils.test.cjs +130 -0
  42. package/tests/emails_utils.test.d.cts +1 -0
  43. package/tests/imageModel.test.cjs +373 -0
  44. package/tests/imageModel.test.d.cts +1 -0
  45. package/tests/multerSharpS3Compat.test.cjs +229 -0
  46. package/tests/multerSharpS3Compat.test.d.cts +1 -0
  47. package/tests/requestCompat.test.cjs +288 -0
  48. package/tests/requestCompat.test.d.cts +1 -0
  49. package/utils/multerSharpS3Compat.cjs +230 -0
  50. package/utils/multerSharpS3Compat.d.cts +22 -0
  51. package/utils/passportSsoCompat.cjs +15 -0
  52. package/utils/passportSsoCompat.d.cts +2 -0
  53. package/utils/recount_utils.cjs +1 -1
  54. package/utils/requestCompat.cjs +180 -0
  55. package/utils/requestCompat.d.cts +9 -0
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ const test = require("node:test");
3
+ const assert = require("node:assert/strict");
4
+ const Module = require("node:module");
5
+ const imageModelPath = require.resolve("../models/image.cjs");
6
+ const loggerPath = require.resolve("../utils/logger.cjs");
7
+ const injectMockModule = (modulePath, moduleExports) => {
8
+ const mockModule = new Module(modulePath);
9
+ mockModule.filename = modulePath;
10
+ mockModule.loaded = true;
11
+ mockModule.exports = moduleExports;
12
+ require.cache[modulePath] = mockModule;
13
+ };
14
+ const loadImageModelFactory = () => {
15
+ const originalLoggerModule = require.cache[loggerPath];
16
+ const originalImageModelModule = require.cache[imageModelPath];
17
+ injectMockModule(loggerPath, {
18
+ error() { },
19
+ info() { },
20
+ warn() { },
21
+ });
22
+ delete require.cache[imageModelPath];
23
+ return {
24
+ createImageModel: require(imageModelPath),
25
+ restore() {
26
+ delete require.cache[imageModelPath];
27
+ if (originalLoggerModule) {
28
+ require.cache[loggerPath] = originalLoggerModule;
29
+ }
30
+ else {
31
+ delete require.cache[loggerPath];
32
+ }
33
+ if (originalImageModelModule) {
34
+ require.cache[imageModelPath] = originalImageModelModule;
35
+ }
36
+ },
37
+ };
38
+ };
39
+ const createDataTypesStub = () => new Proxy({}, {
40
+ get() {
41
+ return {};
42
+ },
43
+ });
44
+ const createResponse = () => {
45
+ return {
46
+ body: null,
47
+ statusCode: null,
48
+ status(code) {
49
+ this.statusCode = code;
50
+ return this;
51
+ },
52
+ json(body) {
53
+ this.body = body;
54
+ return this;
55
+ },
56
+ };
57
+ };
58
+ const setupImageModel = ({ imageRecord, postWithImage, groupWithImage, remainingAssociations, remainingPostHeaderImageCount = 0, createImageModel, associations, }) => {
59
+ const post = {
60
+ cover_media_type: undefined,
61
+ saveCallCount: 0,
62
+ removedHeaderImages: [],
63
+ removedPostImages: [],
64
+ removedUserImages: [],
65
+ async save() {
66
+ this.saveCallCount += 1;
67
+ },
68
+ async removePostHeaderImage(image) {
69
+ this.removedHeaderImages.push(image.id);
70
+ },
71
+ async countPostHeaderImages() {
72
+ return remainingPostHeaderImageCount;
73
+ },
74
+ async removePostImage(image) {
75
+ this.removedPostImages.push(image.id);
76
+ },
77
+ async removePostUserImage(image) {
78
+ this.removedUserImages.push(image.id);
79
+ },
80
+ };
81
+ const group = {
82
+ removedLogoImages: [],
83
+ async removeGroupLogoImage(image) {
84
+ this.removedLogoImages.push(image.id);
85
+ },
86
+ };
87
+ const sequelize = {
88
+ define() {
89
+ return {};
90
+ },
91
+ models: {
92
+ Group: {
93
+ async findByPk() {
94
+ return groupWithImage !== undefined ? group : null;
95
+ },
96
+ async findOne() {
97
+ return groupWithImage;
98
+ },
99
+ },
100
+ Post: {
101
+ async findByPk() {
102
+ return post;
103
+ },
104
+ async findOne() {
105
+ return postWithImage;
106
+ },
107
+ },
108
+ },
109
+ };
110
+ const Image = createImageModel(sequelize, createDataTypesStub());
111
+ sequelize.models.Image = Image;
112
+ Image.associations =
113
+ associations || {
114
+ PostImages: { associationType: "BelongsToMany" },
115
+ PostHeaderImages: { associationType: "BelongsToMany" },
116
+ PostUserImages: { associationType: "BelongsToMany" },
117
+ };
118
+ Image.findByPk = async (_imageId, options) => {
119
+ if (options?.include) {
120
+ return remainingAssociations;
121
+ }
122
+ return imageRecord;
123
+ };
124
+ return { Image, post, group };
125
+ };
126
+ test("removeImageFromCollection detaches shared post header images without deleting the image", async (t) => {
127
+ const { createImageModel, restore } = loadImageModelFactory();
128
+ t.after(restore);
129
+ const imageRecord = {
130
+ id: 42,
131
+ deleted: false,
132
+ saveCallCount: 0,
133
+ async save() {
134
+ this.saveCallCount += 1;
135
+ },
136
+ };
137
+ const { Image, post } = setupImageModel({
138
+ createImageModel,
139
+ imageRecord,
140
+ postWithImage: {
141
+ PostHeaderImages: [{ id: 42 }],
142
+ PostImages: [],
143
+ PostUserImages: [],
144
+ },
145
+ remainingAssociations: {
146
+ PostImages: [],
147
+ PostHeaderImages: [{ id: 42 }],
148
+ PostUserImages: [],
149
+ },
150
+ });
151
+ const res = createResponse();
152
+ await Image.removeImageFromCollection({
153
+ params: {
154
+ imageId: "42",
155
+ postId: "10",
156
+ },
157
+ query: {},
158
+ }, res);
159
+ assert.deepEqual(post.removedHeaderImages, [42]);
160
+ assert.equal(imageRecord.deleted, false);
161
+ assert.equal(imageRecord.saveCallCount, 0);
162
+ assert.equal(res.statusCode, 200);
163
+ assert.deepEqual(res.body, { message: "Image removed from collection" });
164
+ });
165
+ test("removeImageFromCollection still deletes unshared post images", async (t) => {
166
+ const { createImageModel, restore } = loadImageModelFactory();
167
+ t.after(restore);
168
+ const imageRecord = {
169
+ id: 99,
170
+ deleted: false,
171
+ saveCallCount: 0,
172
+ async save() {
173
+ this.saveCallCount += 1;
174
+ },
175
+ };
176
+ const { Image, post } = setupImageModel({
177
+ createImageModel,
178
+ imageRecord,
179
+ postWithImage: {
180
+ PostHeaderImages: [],
181
+ PostImages: [{ id: 99 }],
182
+ PostUserImages: [],
183
+ },
184
+ remainingAssociations: {
185
+ PostImages: [],
186
+ PostHeaderImages: [],
187
+ PostUserImages: [],
188
+ },
189
+ });
190
+ const res = createResponse();
191
+ await Image.removeImageFromCollection({
192
+ params: {
193
+ imageId: "99",
194
+ postId: "10",
195
+ },
196
+ query: {},
197
+ }, res);
198
+ assert.deepEqual(post.removedPostImages, [99]);
199
+ assert.equal(imageRecord.deleted, true);
200
+ assert.equal(imageRecord.saveCallCount, 1);
201
+ assert.equal(res.statusCode, 200);
202
+ assert.deepEqual(res.body, { message: "Image removed from collection" });
203
+ });
204
+ test("removeByUserIdOnly detaches the current collection before preserving shared images", async (t) => {
205
+ const { createImageModel, restore } = loadImageModelFactory();
206
+ t.after(restore);
207
+ const imageRecord = {
208
+ id: 77,
209
+ user_id: 850,
210
+ deleted: false,
211
+ saveCallCount: 0,
212
+ async save() {
213
+ this.saveCallCount += 1;
214
+ },
215
+ };
216
+ const { Image, group } = setupImageModel({
217
+ createImageModel,
218
+ imageRecord,
219
+ groupWithImage: {
220
+ GroupLogoImages: [{ id: 77 }],
221
+ },
222
+ remainingAssociations: {
223
+ GroupLogoImages: [{ id: 77 }],
224
+ CommunityLogoImages: [],
225
+ },
226
+ associations: {
227
+ GroupLogoImages: { associationType: "BelongsToMany" },
228
+ CommunityLogoImages: { associationType: "BelongsToMany" },
229
+ },
230
+ });
231
+ const res = createResponse();
232
+ await Image.removeImageFromCollection({
233
+ params: {
234
+ groupId: "55",
235
+ imageId: "77",
236
+ },
237
+ query: {
238
+ removeByUserIdOnly: "true",
239
+ },
240
+ user: {
241
+ id: 850,
242
+ },
243
+ }, res);
244
+ assert.deepEqual(group.removedLogoImages, [77]);
245
+ assert.equal(imageRecord.deleted, false);
246
+ assert.equal(imageRecord.saveCallCount, 0);
247
+ assert.equal(res.statusCode, 200);
248
+ assert.deepEqual(res.body, { message: "Image removed from collection" });
249
+ });
250
+ test("removeByUserIdOnly directly deletes unattached uploads owned by the current user", async (t) => {
251
+ const { createImageModel, restore } = loadImageModelFactory();
252
+ t.after(restore);
253
+ const imageRecord = {
254
+ id: 88,
255
+ user_id: 850,
256
+ deleted: false,
257
+ saveCallCount: 0,
258
+ async save() {
259
+ this.saveCallCount += 1;
260
+ },
261
+ };
262
+ const { Image, group } = setupImageModel({
263
+ createImageModel,
264
+ imageRecord,
265
+ groupWithImage: null,
266
+ remainingAssociations: {
267
+ GroupLogoImages: [],
268
+ },
269
+ associations: {
270
+ GroupLogoImages: { associationType: "BelongsToMany" },
271
+ },
272
+ });
273
+ const res = createResponse();
274
+ await Image.removeImageFromCollection({
275
+ params: {
276
+ groupId: "55",
277
+ imageId: "88",
278
+ },
279
+ query: {
280
+ removeByUserIdOnly: "true",
281
+ },
282
+ user: {
283
+ id: 850,
284
+ },
285
+ }, res);
286
+ assert.deepEqual(group.removedLogoImages, []);
287
+ assert.equal(imageRecord.deleted, true);
288
+ assert.equal(imageRecord.saveCallCount, 1);
289
+ assert.equal(res.statusCode, 200);
290
+ assert.deepEqual(res.body, { message: "Image removed from collection" });
291
+ });
292
+ test("removing the only post header image resets cover_media_type", async (t) => {
293
+ const { createImageModel, restore } = loadImageModelFactory();
294
+ t.after(restore);
295
+ const imageRecord = {
296
+ id: 123,
297
+ deleted: false,
298
+ saveCallCount: 0,
299
+ async save() {
300
+ this.saveCallCount += 1;
301
+ },
302
+ };
303
+ const { Image, post } = setupImageModel({
304
+ createImageModel,
305
+ imageRecord,
306
+ postWithImage: {
307
+ PostHeaderImages: [{ id: 123 }],
308
+ PostImages: [],
309
+ PostUserImages: [],
310
+ },
311
+ remainingAssociations: {
312
+ PostImages: [],
313
+ PostHeaderImages: [],
314
+ PostUserImages: [],
315
+ },
316
+ remainingPostHeaderImageCount: 0,
317
+ });
318
+ post.cover_media_type = "image";
319
+ const res = createResponse();
320
+ await Image.removeImageFromCollection({
321
+ params: {
322
+ imageId: "123",
323
+ postId: "10",
324
+ },
325
+ query: {},
326
+ }, res);
327
+ assert.deepEqual(post.removedHeaderImages, [123]);
328
+ assert.equal(post.cover_media_type, "none");
329
+ assert.equal(post.saveCallCount, 1);
330
+ assert.equal(res.statusCode, 200);
331
+ assert.deepEqual(res.body, { message: "Image removed from collection" });
332
+ });
333
+ test("removing one post header image keeps cover_media_type when others remain", async (t) => {
334
+ const { createImageModel, restore } = loadImageModelFactory();
335
+ t.after(restore);
336
+ const imageRecord = {
337
+ id: 124,
338
+ deleted: false,
339
+ saveCallCount: 0,
340
+ async save() {
341
+ this.saveCallCount += 1;
342
+ },
343
+ };
344
+ const { Image, post } = setupImageModel({
345
+ createImageModel,
346
+ imageRecord,
347
+ postWithImage: {
348
+ PostHeaderImages: [{ id: 124 }],
349
+ PostImages: [],
350
+ PostUserImages: [],
351
+ },
352
+ remainingAssociations: {
353
+ PostImages: [],
354
+ PostHeaderImages: [{ id: 999 }],
355
+ PostUserImages: [],
356
+ },
357
+ remainingPostHeaderImageCount: 1,
358
+ });
359
+ post.cover_media_type = "image";
360
+ const res = createResponse();
361
+ await Image.removeImageFromCollection({
362
+ params: {
363
+ imageId: "124",
364
+ postId: "10",
365
+ },
366
+ query: {},
367
+ }, res);
368
+ assert.deepEqual(post.removedHeaderImages, [124]);
369
+ assert.equal(post.cover_media_type, "image");
370
+ assert.equal(post.saveCallCount, 0);
371
+ assert.equal(res.statusCode, 200);
372
+ assert.deepEqual(res.body, { message: "Image removed from collection" });
373
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ const test = require("node:test");
3
+ const assert = require("node:assert/strict");
4
+ const Module = require("node:module");
5
+ const { PassThrough } = require("node:stream");
6
+ const sharpPath = require.resolve("sharp");
7
+ const mimeTypesPath = require.resolve("mime-types");
8
+ const multerSharpS3CompatPath = require.resolve("../utils/multerSharpS3Compat.cjs");
9
+ const injectMockModule = (modulePath, moduleExports) => {
10
+ const mockModule = new Module(modulePath);
11
+ mockModule.filename = modulePath;
12
+ mockModule.loaded = true;
13
+ mockModule.exports = moduleExports;
14
+ require.cache[modulePath] = mockModule;
15
+ };
16
+ const loadMulterSharpS3Compat = ({ mockSharp, mockMimeTypes }) => {
17
+ const originalSharpModule = require.cache[sharpPath];
18
+ const originalMimeTypesModule = require.cache[mimeTypesPath];
19
+ const originalCompatModule = require.cache[multerSharpS3CompatPath];
20
+ injectMockModule(sharpPath, mockSharp);
21
+ injectMockModule(mimeTypesPath, mockMimeTypes);
22
+ delete require.cache[multerSharpS3CompatPath];
23
+ const multerSharpS3Compat = require(multerSharpS3CompatPath);
24
+ return {
25
+ multerSharpS3Compat,
26
+ restore() {
27
+ delete require.cache[multerSharpS3CompatPath];
28
+ if (originalSharpModule) {
29
+ require.cache[sharpPath] = originalSharpModule;
30
+ }
31
+ else {
32
+ delete require.cache[sharpPath];
33
+ }
34
+ if (originalMimeTypesModule) {
35
+ require.cache[mimeTypesPath] = originalMimeTypesModule;
36
+ }
37
+ else {
38
+ delete require.cache[mimeTypesPath];
39
+ }
40
+ if (originalCompatModule) {
41
+ require.cache[multerSharpS3CompatPath] = originalCompatModule;
42
+ }
43
+ },
44
+ };
45
+ };
46
+ const createMockSharp = (requestedFormats) => {
47
+ return () => {
48
+ let selectedFormat = null;
49
+ const stream = new PassThrough();
50
+ stream.resize = () => stream;
51
+ stream.toFormat = (format) => {
52
+ selectedFormat = format;
53
+ requestedFormats.push(format);
54
+ return stream;
55
+ };
56
+ process.nextTick(() => {
57
+ stream.end(Buffer.from(`formatted:${selectedFormat || "original"}`));
58
+ });
59
+ return stream;
60
+ };
61
+ };
62
+ const createMockS3 = ({ failKeys = [] } = {}) => {
63
+ const uploads = [];
64
+ const deletedObjects = [];
65
+ return {
66
+ uploads,
67
+ deletedObjects,
68
+ s3: {
69
+ upload(params) {
70
+ uploads.push(params);
71
+ return {
72
+ promise() {
73
+ return new Promise((resolve, reject) => {
74
+ const chunks = [];
75
+ params.Body.on("data", (chunk) => {
76
+ chunks.push(chunk);
77
+ });
78
+ params.Body.on("error", reject);
79
+ params.Body.on("end", () => {
80
+ if (failKeys.includes(params.Key)) {
81
+ reject(new Error(`upload failed for ${params.Key}`));
82
+ }
83
+ else {
84
+ resolve({
85
+ Bucket: params.Bucket,
86
+ Key: params.Key,
87
+ Location: `https://example.com/${params.Key}`,
88
+ uploadedBody: Buffer.concat(chunks).toString("utf8"),
89
+ });
90
+ }
91
+ });
92
+ });
93
+ },
94
+ };
95
+ },
96
+ deleteObject(params) {
97
+ deletedObjects.push(params);
98
+ return {
99
+ promise() {
100
+ return Promise.resolve();
101
+ },
102
+ };
103
+ },
104
+ },
105
+ };
106
+ };
107
+ test("multerSharpS3Compat honors file.outputFormat over the default format", async (t) => {
108
+ const requestedFormats = [];
109
+ const { s3, uploads } = createMockS3();
110
+ const { multerSharpS3Compat, restore } = loadMulterSharpS3Compat({
111
+ mockSharp: createMockSharp(requestedFormats),
112
+ mockMimeTypes: {
113
+ contentType(format) {
114
+ return `image/${format}`;
115
+ },
116
+ },
117
+ });
118
+ t.after(restore);
119
+ const storage = multerSharpS3Compat({
120
+ s3,
121
+ Bucket: "images",
122
+ toFormat: "png",
123
+ Key: "animated.gif",
124
+ });
125
+ const fileStream = new PassThrough();
126
+ fileStream.end(Buffer.from("source"));
127
+ const file = {
128
+ mimetype: "image/gif",
129
+ originalname: "animated.gif",
130
+ outputFormat: "gif",
131
+ stream: fileStream,
132
+ };
133
+ const result = await new Promise((resolve, reject) => {
134
+ storage._handleFile({}, file, (error, value) => {
135
+ if (error) {
136
+ reject(error);
137
+ }
138
+ else {
139
+ resolve(value);
140
+ }
141
+ });
142
+ });
143
+ assert.deepEqual(requestedFormats, ["gif"]);
144
+ assert.equal(uploads.length, 1);
145
+ assert.equal(uploads[0].ContentType, "image/gif");
146
+ assert.equal(result.default.ContentType, "image/gif");
147
+ assert.equal(result.mimetype, "image/gif");
148
+ });
149
+ test("multerSharpS3Compat rolls back earlier variants when a later upload fails", async (t) => {
150
+ const requestedFormats = [];
151
+ const failingKey = "up/animated-large-2.gif";
152
+ const { s3, deletedObjects } = createMockS3({ failKeys: [failingKey] });
153
+ const { multerSharpS3Compat, restore } = loadMulterSharpS3Compat({
154
+ mockSharp: createMockSharp(requestedFormats),
155
+ mockMimeTypes: {
156
+ contentType(format) {
157
+ return `image/${format}`;
158
+ },
159
+ },
160
+ });
161
+ t.after(restore);
162
+ const storage = multerSharpS3Compat({
163
+ s3,
164
+ Bucket: "images",
165
+ toFormat: "png",
166
+ Key: "animated.gif",
167
+ multiple: true,
168
+ resize: [
169
+ { suffix: "-small-1", directory: "up" },
170
+ { suffix: "-large-2", directory: "up" },
171
+ ],
172
+ });
173
+ const fileStream = new PassThrough();
174
+ fileStream.end(Buffer.from("source"));
175
+ const file = {
176
+ mimetype: "image/gif",
177
+ originalname: "animated.gif",
178
+ outputFormat: "gif",
179
+ stream: fileStream,
180
+ };
181
+ const error = await new Promise((resolve) => {
182
+ storage._handleFile({}, file, (uploadError) => {
183
+ resolve(uploadError);
184
+ });
185
+ });
186
+ assert.match(error.message, /upload failed/);
187
+ assert.deepEqual(requestedFormats, ["gif", "gif"]);
188
+ assert.deepEqual(deletedObjects, [
189
+ {
190
+ Bucket: "images",
191
+ Key: "up/animated-small-1.gif",
192
+ },
193
+ ]);
194
+ });
195
+ test("_removeFile deletes every uploaded variant in a multi-variant upload", async (t) => {
196
+ const { s3, deletedObjects } = createMockS3();
197
+ const { multerSharpS3Compat, restore } = loadMulterSharpS3Compat({
198
+ mockSharp: createMockSharp([]),
199
+ mockMimeTypes: {
200
+ contentType(format) {
201
+ return `image/${format}`;
202
+ },
203
+ },
204
+ });
205
+ t.after(restore);
206
+ const storage = multerSharpS3Compat({
207
+ s3,
208
+ Bucket: "images",
209
+ Key: "animated.gif",
210
+ });
211
+ await new Promise((resolve, reject) => {
212
+ storage._removeFile({}, {
213
+ default: { Bucket: "images", Key: "up/a.gif" },
214
+ "-small-1": { Bucket: "images", Key: "up/a-small.gif" },
215
+ mimetype: "image/gif",
216
+ }, (error) => {
217
+ if (error) {
218
+ reject(error);
219
+ }
220
+ else {
221
+ resolve();
222
+ }
223
+ });
224
+ });
225
+ assert.deepEqual(deletedObjects, [
226
+ { Bucket: "images", Key: "up/a.gif" },
227
+ { Bucket: "images", Key: "up/a-small.gif" },
228
+ ]);
229
+ });
@@ -0,0 +1 @@
1
+ export {};