@quatrain/worker 1.2.7 → 1.2.9

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.
@@ -88,17 +88,17 @@ describe('FileSystem Utilities (Worker)', () => {
88
88
  return node_stream_1.Readable.from(Buffer.alloc(35 * 1024));
89
89
  });
90
90
  jest.spyOn(node_fs_1.default, 'existsSync').mockImplementation((p) => {
91
- if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4'))) {
91
+ if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
92
92
  return true;
93
93
  }
94
94
  return originalExistsSync(p);
95
95
  });
96
96
  jest.spyOn(node_fs_1.default, 'statSync').mockImplementation((p) => {
97
97
  if (typeof p === 'string') {
98
- if (p.endsWith('small.mp4')) {
98
+ if (p.endsWith('small.mp4') || p.endsWith('small-fail.mp4')) {
99
99
  return { size: 13 };
100
100
  }
101
- if (p.endsWith('large.mp4') || p.endsWith('large-fail.mp4')) {
101
+ if (p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('large-sync-fail.mp4')) {
102
102
  return { size: 35 * 1024 };
103
103
  }
104
104
  }
@@ -252,8 +252,18 @@ describe('FileSystem Utilities (Worker)', () => {
252
252
  ffmpeg.ffprobe.mockImplementation((file, callback) => {
253
253
  callback(null, mockMetadata);
254
254
  });
255
- node_fetch_native_1.default.mockResolvedValue({
256
- ok: true
255
+ node_fetch_native_1.default.mockImplementation((url, options) => {
256
+ return new Promise((resolve) => {
257
+ if (options.body && typeof options.body.on === 'function') {
258
+ options.body.on('data', () => { });
259
+ options.body.on('end', () => {
260
+ resolve({ ok: true });
261
+ });
262
+ }
263
+ else {
264
+ resolve({ ok: true });
265
+ }
266
+ });
257
267
  });
258
268
  const meta = {
259
269
  bucket: 'mock-bucket',
@@ -281,5 +291,42 @@ describe('FileSystem Utilities (Worker)', () => {
281
291
  };
282
292
  await expect(FileSystem_1.FileSystem.uploadFile(testFile, meta)).rejects.toThrow('Connection abort');
283
293
  });
294
+ it('should reject small uploads if HTTP request fails', async () => {
295
+ const testFile = node_path_1.default.join(testBaseDir, 'small-fail.mp4');
296
+ const mockMetadata = {
297
+ streams: [{ width: 320, height: 240, duration: '1', bit_rate: '100', nb_frames: '10' }]
298
+ };
299
+ ffmpeg.ffprobe.mockImplementation((file, callback) => {
300
+ callback(null, mockMetadata);
301
+ });
302
+ node_fetch_native_1.default.mockRejectedValue(new Error('Upload failed'));
303
+ // Stub readFileSync for small content read
304
+ jest.spyOn(node_fs_1.default, 'readFileSync').mockReturnValue(Buffer.from('small content'));
305
+ const meta = {
306
+ bucket: 'mock-bucket',
307
+ ref: 'remote/small-fail.mp4',
308
+ uploadUrl: 'https://upload.com/small-fail.mp4'
309
+ };
310
+ await expect(FileSystem_1.FileSystem.uploadFile(testFile, meta)).rejects.toThrow('Upload failed');
311
+ });
312
+ it('should handle synchronous errors inside stream initialization', async () => {
313
+ const testFile = node_path_1.default.join(testBaseDir, 'large-sync-fail.mp4');
314
+ const mockMetadata = {
315
+ streams: [{ width: 1280, height: 720, duration: '5', bit_rate: '2000', nb_frames: '150' }]
316
+ };
317
+ ffmpeg.ffprobe.mockImplementation((file, callback) => {
318
+ callback(null, mockMetadata);
319
+ });
320
+ // Make createReadStream throw a synchronous error
321
+ jest.spyOn(node_fs_1.default, 'createReadStream').mockImplementationOnce(() => {
322
+ throw new Error('Sync stream error');
323
+ });
324
+ const meta = {
325
+ bucket: 'mock-bucket',
326
+ ref: 'remote/large-sync-fail.mp4',
327
+ uploadUrl: 'https://upload.com/large-sync-fail.mp4'
328
+ };
329
+ await expect(FileSystem_1.FileSystem.uploadFile(testFile, meta)).rejects.toThrow('Sync stream error');
330
+ });
284
331
  });
285
332
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Helpers_1 = require("./Helpers");
4
+ const Worker_1 = require("./Worker");
5
+ jest.mock('./Worker', () => {
6
+ const original = jest.requireActual('./Worker');
7
+ return {
8
+ Worker: {
9
+ ...original.Worker,
10
+ getSystemCommandPath: jest.fn().mockResolvedValue('/usr/bin/ffmpeg'),
11
+ execPromise: jest.fn().mockResolvedValue(true),
12
+ info: jest.fn(),
13
+ }
14
+ };
15
+ });
16
+ describe('Helpers', () => {
17
+ beforeEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+ it('should generate video thumbnail parameters correctly and execute ffmpeg', async () => {
21
+ const videoPath = 'test.mp4';
22
+ const outputPath = 'output.jpg';
23
+ const result = await Helpers_1.Helpers.generateVideoThumbnail(videoPath, outputPath, 5, 640);
24
+ expect(result).toBe(true);
25
+ expect(Worker_1.Worker.execPromise).toHaveBeenCalledWith('/usr/bin/ffmpeg', [
26
+ '-i',
27
+ 'test.mp4',
28
+ '-vframes',
29
+ '1',
30
+ '-vf',
31
+ 'select=gte(n\\,5)',
32
+ '-s',
33
+ '640x480',
34
+ '-ss',
35
+ '1',
36
+ 'output.jpg',
37
+ '-y'
38
+ ]);
39
+ });
40
+ it('should use default values for frame and width', async () => {
41
+ const videoPath = 'test.mp4';
42
+ const outputPath = 'output.jpg';
43
+ const result = await Helpers_1.Helpers.generateVideoThumbnail(videoPath, outputPath);
44
+ expect(result).toBe(true);
45
+ expect(Worker_1.Worker.execPromise).toHaveBeenCalledWith('/usr/bin/ffmpeg', [
46
+ '-i',
47
+ 'test.mp4',
48
+ '-vframes',
49
+ '1',
50
+ '-vf',
51
+ 'select=gte(n\\,0)',
52
+ '-s',
53
+ '320x240',
54
+ '-ss',
55
+ '1',
56
+ 'output.jpg',
57
+ '-y'
58
+ ]);
59
+ });
60
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quatrain/worker",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "Container Worker helpers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "typescript": "^5.1.5"
35
35
  },
36
36
  "dependencies": {
37
- "@quatrain/core": "^1.2.14",
37
+ "@quatrain/core": "^1.2.15",
38
38
  "@quatrain/queue": "^1.2.3",
39
39
  "@quatrain/storage": "^1.2.9",
40
40
  "axios": "^1.7.7",
@@ -57,7 +57,7 @@ describe('FileSystem Utilities (Worker)', () => {
57
57
  })
58
58
 
59
59
  jest.spyOn(fs, 'existsSync').mockImplementation((p) => {
60
- if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4'))) {
60
+ if (typeof p === 'string' && (p.endsWith('test.txt') || p.endsWith('small.mp4') || p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('small-fail.mp4') || p.endsWith('large-sync-fail.mp4'))) {
61
61
  return true
62
62
  }
63
63
  return originalExistsSync(p)
@@ -65,10 +65,10 @@ describe('FileSystem Utilities (Worker)', () => {
65
65
 
66
66
  jest.spyOn(fs, 'statSync').mockImplementation((p) => {
67
67
  if (typeof p === 'string') {
68
- if (p.endsWith('small.mp4')) {
68
+ if (p.endsWith('small.mp4') || p.endsWith('small-fail.mp4')) {
69
69
  return { size: 13 } as any
70
70
  }
71
- if (p.endsWith('large.mp4') || p.endsWith('large-fail.mp4')) {
71
+ if (p.endsWith('large.mp4') || p.endsWith('large-fail.mp4') || p.endsWith('large-sync-fail.mp4')) {
72
72
  return { size: 35 * 1024 } as any
73
73
  }
74
74
  }
@@ -255,8 +255,17 @@ describe('FileSystem Utilities (Worker)', () => {
255
255
  callback(null, mockMetadata)
256
256
  });
257
257
 
258
- (fetch as any).mockResolvedValue({
259
- ok: true
258
+ (fetch as any).mockImplementation((url: string, options: any) => {
259
+ return new Promise((resolve) => {
260
+ if (options.body && typeof options.body.on === 'function') {
261
+ options.body.on('data', () => {})
262
+ options.body.on('end', () => {
263
+ resolve({ ok: true })
264
+ })
265
+ } else {
266
+ resolve({ ok: true })
267
+ }
268
+ })
260
269
  })
261
270
 
262
271
  const meta = {
@@ -291,5 +300,53 @@ describe('FileSystem Utilities (Worker)', () => {
291
300
 
292
301
  await expect(FileSystem.uploadFile(testFile, meta)).rejects.toThrow('Connection abort')
293
302
  })
303
+
304
+ it('should reject small uploads if HTTP request fails', async () => {
305
+ const testFile = path.join(testBaseDir, 'small-fail.mp4')
306
+
307
+ const mockMetadata = {
308
+ streams: [{ width: 320, height: 240, duration: '1', bit_rate: '100', nb_frames: '10' }]
309
+ };
310
+ (ffmpeg.ffprobe as any).mockImplementation((file: string, callback: any) => {
311
+ callback(null, mockMetadata)
312
+ });
313
+
314
+ (fetch as any).mockRejectedValue(new Error('Upload failed'))
315
+
316
+ // Stub readFileSync for small content read
317
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(Buffer.from('small content'))
318
+
319
+ const meta = {
320
+ bucket: 'mock-bucket',
321
+ ref: 'remote/small-fail.mp4',
322
+ uploadUrl: 'https://upload.com/small-fail.mp4'
323
+ }
324
+
325
+ await expect(FileSystem.uploadFile(testFile, meta)).rejects.toThrow('Upload failed')
326
+ })
327
+
328
+ it('should handle synchronous errors inside stream initialization', async () => {
329
+ const testFile = path.join(testBaseDir, 'large-sync-fail.mp4')
330
+
331
+ const mockMetadata = {
332
+ streams: [{ width: 1280, height: 720, duration: '5', bit_rate: '2000', nb_frames: '150' }]
333
+ };
334
+ (ffmpeg.ffprobe as any).mockImplementation((file: string, callback: any) => {
335
+ callback(null, mockMetadata)
336
+ });
337
+
338
+ // Make createReadStream throw a synchronous error
339
+ jest.spyOn(fs, 'createReadStream').mockImplementationOnce(() => {
340
+ throw new Error('Sync stream error')
341
+ })
342
+
343
+ const meta = {
344
+ bucket: 'mock-bucket',
345
+ ref: 'remote/large-sync-fail.mp4',
346
+ uploadUrl: 'https://upload.com/large-sync-fail.mp4'
347
+ }
348
+
349
+ await expect(FileSystem.uploadFile(testFile, meta)).rejects.toThrow('Sync stream error')
350
+ })
294
351
  })
295
352
  })
@@ -0,0 +1,66 @@
1
+ import { Helpers } from './Helpers'
2
+ import { Worker } from './Worker'
3
+
4
+ jest.mock('./Worker', () => {
5
+ const original = jest.requireActual('./Worker')
6
+ return {
7
+ Worker: {
8
+ ...original.Worker,
9
+ getSystemCommandPath: jest.fn().mockResolvedValue('/usr/bin/ffmpeg'),
10
+ execPromise: jest.fn().mockResolvedValue(true),
11
+ info: jest.fn(),
12
+ }
13
+ }
14
+ })
15
+
16
+ describe('Helpers', () => {
17
+ beforeEach(() => {
18
+ jest.clearAllMocks()
19
+ })
20
+
21
+ it('should generate video thumbnail parameters correctly and execute ffmpeg', async () => {
22
+ const videoPath = 'test.mp4'
23
+ const outputPath = 'output.jpg'
24
+
25
+ const result = await Helpers.generateVideoThumbnail(videoPath, outputPath, 5, 640)
26
+
27
+ expect(result).toBe(true)
28
+ expect(Worker.execPromise).toHaveBeenCalledWith('/usr/bin/ffmpeg', [
29
+ '-i',
30
+ 'test.mp4',
31
+ '-vframes',
32
+ '1',
33
+ '-vf',
34
+ 'select=gte(n\\,5)',
35
+ '-s',
36
+ '640x480',
37
+ '-ss',
38
+ '1',
39
+ 'output.jpg',
40
+ '-y'
41
+ ])
42
+ })
43
+
44
+ it('should use default values for frame and width', async () => {
45
+ const videoPath = 'test.mp4'
46
+ const outputPath = 'output.jpg'
47
+
48
+ const result = await Helpers.generateVideoThumbnail(videoPath, outputPath)
49
+
50
+ expect(result).toBe(true)
51
+ expect(Worker.execPromise).toHaveBeenCalledWith('/usr/bin/ffmpeg', [
52
+ '-i',
53
+ 'test.mp4',
54
+ '-vframes',
55
+ '1',
56
+ '-vf',
57
+ 'select=gte(n\\,0)',
58
+ '-s',
59
+ '320x240',
60
+ '-ss',
61
+ '1',
62
+ 'output.jpg',
63
+ '-y'
64
+ ])
65
+ })
66
+ })