@quatrain/worker 1.2.5 → 1.2.6

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.
@@ -135,9 +135,10 @@ class FileSystem {
135
135
  * @param mime
136
136
  * @returns Promise
137
137
  */
138
- static uploadFile(filename, meta, mime = 'video/mp4') {
138
+ static async uploadFile(filename, meta, mime = 'video/mp4') {
139
139
  // try to get more file metadata
140
- meta = { ...meta, ...FileSystem.getInfo(filename) };
140
+ const info = await FileSystem.getInfo(filename);
141
+ meta = { ...meta, ...info };
141
142
  Worker_1.Worker.info(`Uploading file ${filename} to ${meta.uploadUrl}`);
142
143
  const { size } = node_fs_1.default.statSync(filename);
143
144
  if (size < 32 * 1024) {
@@ -9,6 +9,18 @@ const node_child_process_1 = require("node:child_process");
9
9
  // Mock dependencies
10
10
  jest.mock('axios');
11
11
  jest.mock('node:child_process');
12
+ jest.mock('@quatrain/queue', () => {
13
+ const mockQueueInstance = {
14
+ listen: jest.fn(),
15
+ };
16
+ return {
17
+ Queue: {
18
+ addQueue: jest.fn(),
19
+ getQueue: jest.fn(() => mockQueueInstance),
20
+ info: jest.fn(),
21
+ },
22
+ };
23
+ }, { virtual: true });
12
24
  const mockedAxios = axios_1.default;
13
25
  const mockedSpawn = node_child_process_1.spawn;
14
26
  describe('Worker', () => {
@@ -77,6 +89,25 @@ describe('Worker', () => {
77
89
  metadata: { size: 1024 },
78
90
  }));
79
91
  });
92
+ it('should execute then block on success in pushEvent', async () => {
93
+ Worker_1.Worker.endpoint = 'https://api.example.com/events';
94
+ mockedAxios.patch.mockResolvedValue({
95
+ statusText: 'OK',
96
+ data: { success: true },
97
+ });
98
+ Worker_1.Worker.pushEvent('test-event');
99
+ // Allow microtasks to run so .then is executed
100
+ await new Promise((resolve) => setTimeout(resolve, 0));
101
+ expect(mockedAxios.patch).toHaveBeenCalled();
102
+ });
103
+ it('should handle axios error in pushEvent', async () => {
104
+ Worker_1.Worker.endpoint = 'https://api.example.com/events';
105
+ mockedAxios.patch.mockRejectedValue(new Error('Network error'));
106
+ Worker_1.Worker.pushEvent('test-event');
107
+ // Allow microtasks to run so .catch is executed
108
+ await new Promise((resolve) => setTimeout(resolve, 0));
109
+ expect(mockedAxios.patch).toHaveBeenCalled();
110
+ });
80
111
  });
81
112
  describe('pushEventAsync', () => {
82
113
  it('should return false when endpoint is not set', async () => {
@@ -213,6 +244,83 @@ describe('Worker', () => {
213
244
  shell: false,
214
245
  });
215
246
  });
247
+ it('should throw an error when spawn itself throws', async () => {
248
+ mockedSpawn.mockImplementationOnce(() => {
249
+ throw new Error('Spawn failure');
250
+ });
251
+ await expect(Worker_1.Worker.execPromise('cmd')).rejects.toThrow('Spawn failure');
252
+ });
253
+ it('should throw and log when an error occurs before the promise constructor', async () => {
254
+ const spyInfo = jest.spyOn(Worker_1.Worker, 'info').mockImplementationOnce(() => {
255
+ throw new Error('Before promise error');
256
+ });
257
+ await expect(() => Worker_1.Worker.execPromise('cmd')).toThrow('Before promise error');
258
+ spyInfo.mockRestore();
259
+ });
260
+ });
261
+ describe('handler', () => {
262
+ let originalExit;
263
+ beforeAll(() => {
264
+ originalExit = process.exit;
265
+ process.exit = jest.fn();
266
+ });
267
+ afterAll(() => {
268
+ process.exit = originalExit;
269
+ });
270
+ beforeEach(() => {
271
+ jest.clearAllMocks();
272
+ delete process.env.JSON;
273
+ });
274
+ it('should listen to queue in queue mode', async () => {
275
+ const messageHandler = jest.fn();
276
+ const config = {
277
+ mode: 'queue',
278
+ topic: 'test-topic',
279
+ queueAdapter: 'test-adapter',
280
+ concurrency: 5,
281
+ gpu: false,
282
+ };
283
+ const { Queue } = require('@quatrain/queue');
284
+ await Worker_1.Worker.handler(messageHandler, config);
285
+ expect(Queue.addQueue).toHaveBeenCalledWith('test-adapter', 'default', true);
286
+ expect(Queue.getQueue().listen).toHaveBeenCalledWith('test-topic', messageHandler, {
287
+ concurrency: 5,
288
+ gpu: false,
289
+ });
290
+ });
291
+ it('should handle test mode and call messageHandler', async () => {
292
+ const messageHandler = jest.fn();
293
+ const config = {
294
+ mode: 'test',
295
+ };
296
+ await Worker_1.Worker.handler(messageHandler, config);
297
+ expect(messageHandler).toHaveBeenCalledWith(expect.objectContaining({ dummy: 'test-data' }));
298
+ });
299
+ it('should handle cli mode with environment variable', async () => {
300
+ const messageHandler = jest.fn();
301
+ process.env.JSON = JSON.stringify({ cli: 'data' });
302
+ const config = {
303
+ mode: 'cli',
304
+ };
305
+ await Worker_1.Worker.handler(messageHandler, config);
306
+ expect(messageHandler).toHaveBeenCalledWith(JSON.stringify({ cli: 'data' }));
307
+ });
308
+ it('should throw when cli mode has no environment variable', async () => {
309
+ const messageHandler = jest.fn();
310
+ const config = {
311
+ mode: 'cli',
312
+ };
313
+ await Worker_1.Worker.handler(messageHandler, config);
314
+ expect(process.exit).toHaveBeenCalledWith(1);
315
+ });
316
+ it('should exit when mode is unknown', async () => {
317
+ const messageHandler = jest.fn();
318
+ const config = {
319
+ mode: 'unknown',
320
+ };
321
+ await Worker_1.Worker.handler(messageHandler, config);
322
+ expect(process.exit).toHaveBeenCalledWith(1);
323
+ });
216
324
  });
217
325
  describe('logger', () => {
218
326
  it('should have a logger instance', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quatrain/worker",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
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.9",
37
+ "@quatrain/core": "^1.2.14",
38
38
  "@quatrain/queue": "^1.2.3",
39
39
  "@quatrain/storage": "^1.2.8",
40
40
  "axios": "^1.7.7",
package/src/FileSystem.ts CHANGED
@@ -105,13 +105,14 @@ export class FileSystem {
105
105
  * @param mime
106
106
  * @returns Promise
107
107
  */
108
- static uploadFile(
108
+ static async uploadFile(
109
109
  filename: string,
110
110
  meta: FileType,
111
111
  mime = 'video/mp4'
112
112
  ): Promise<FileType> {
113
113
  // try to get more file metadata
114
- meta = { ...meta, ...FileSystem.getInfo(filename) }
114
+ const info = await FileSystem.getInfo(filename)
115
+ meta = { ...meta, ...info }
115
116
  Worker.info(`Uploading file ${filename} to ${meta.uploadUrl}`)
116
117
  const { size } = fs.statSync(filename)
117
118
 
@@ -5,6 +5,18 @@ import { spawn } from 'node:child_process'
5
5
  // Mock dependencies
6
6
  jest.mock('axios')
7
7
  jest.mock('node:child_process')
8
+ jest.mock('@quatrain/queue', () => {
9
+ const mockQueueInstance = {
10
+ listen: jest.fn(),
11
+ }
12
+ return {
13
+ Queue: {
14
+ addQueue: jest.fn(),
15
+ getQueue: jest.fn(() => mockQueueInstance),
16
+ info: jest.fn(),
17
+ },
18
+ }
19
+ }, { virtual: true })
8
20
 
9
21
  const mockedAxios = axios as jest.Mocked<typeof axios>
10
22
  const mockedSpawn = spawn as jest.MockedFunction<typeof spawn>
@@ -101,6 +113,35 @@ describe('Worker', () => {
101
113
  })
102
114
  )
103
115
  })
116
+
117
+ it('should execute then block on success in pushEvent', async () => {
118
+ Worker.endpoint = 'https://api.example.com/events'
119
+
120
+ mockedAxios.patch.mockResolvedValue({
121
+ statusText: 'OK',
122
+ data: { success: true },
123
+ })
124
+
125
+ Worker.pushEvent('test-event')
126
+
127
+ // Allow microtasks to run so .then is executed
128
+ await new Promise((resolve) => setTimeout(resolve, 0))
129
+
130
+ expect(mockedAxios.patch).toHaveBeenCalled()
131
+ })
132
+
133
+ it('should handle axios error in pushEvent', async () => {
134
+ Worker.endpoint = 'https://api.example.com/events'
135
+
136
+ mockedAxios.patch.mockRejectedValue(new Error('Network error'))
137
+
138
+ Worker.pushEvent('test-event')
139
+
140
+ // Allow microtasks to run so .catch is executed
141
+ await new Promise((resolve) => setTimeout(resolve, 0))
142
+
143
+ expect(mockedAxios.patch).toHaveBeenCalled()
144
+ })
104
145
  })
105
146
 
106
147
  describe('pushEventAsync', () => {
@@ -282,6 +323,109 @@ describe('Worker', () => {
282
323
  shell: false,
283
324
  })
284
325
  })
326
+
327
+ it('should throw an error when spawn itself throws', async () => {
328
+ mockedSpawn.mockImplementationOnce(() => {
329
+ throw new Error('Spawn failure')
330
+ })
331
+
332
+ await expect(Worker.execPromise('cmd')).rejects.toThrow('Spawn failure')
333
+ })
334
+
335
+ it('should throw and log when an error occurs before the promise constructor', async () => {
336
+ const spyInfo = jest.spyOn(Worker, 'info').mockImplementationOnce(() => {
337
+ throw new Error('Before promise error')
338
+ })
339
+
340
+ await expect(() => Worker.execPromise('cmd')).toThrow('Before promise error')
341
+ spyInfo.mockRestore()
342
+ })
343
+ })
344
+
345
+ describe('handler', () => {
346
+ let originalExit: any
347
+
348
+ beforeAll(() => {
349
+ originalExit = process.exit
350
+ process.exit = jest.fn() as any
351
+ })
352
+
353
+ afterAll(() => {
354
+ process.exit = originalExit
355
+ })
356
+
357
+ beforeEach(() => {
358
+ jest.clearAllMocks()
359
+ delete process.env.JSON
360
+ })
361
+
362
+ it('should listen to queue in queue mode', async () => {
363
+ const messageHandler = jest.fn()
364
+ const config = {
365
+ mode: 'queue' as const,
366
+ topic: 'test-topic',
367
+ queueAdapter: 'test-adapter' as any,
368
+ concurrency: 5,
369
+ gpu: false,
370
+ }
371
+
372
+ const { Queue } = require('@quatrain/queue')
373
+
374
+ await Worker.handler(messageHandler, config)
375
+
376
+ expect(Queue.addQueue).toHaveBeenCalledWith('test-adapter', 'default', true)
377
+ expect(Queue.getQueue().listen).toHaveBeenCalledWith('test-topic', messageHandler, {
378
+ concurrency: 5,
379
+ gpu: false,
380
+ })
381
+ })
382
+
383
+ it('should handle test mode and call messageHandler', async () => {
384
+ const messageHandler = jest.fn()
385
+ const config = {
386
+ mode: 'test' as const,
387
+ }
388
+
389
+ await Worker.handler(messageHandler, config)
390
+
391
+ expect(messageHandler).toHaveBeenCalledWith(
392
+ expect.objectContaining({ dummy: 'test-data' })
393
+ )
394
+ })
395
+
396
+ it('should handle cli mode with environment variable', async () => {
397
+ const messageHandler = jest.fn()
398
+ process.env.JSON = JSON.stringify({ cli: 'data' })
399
+ const config = {
400
+ mode: 'cli' as const,
401
+ }
402
+
403
+ await Worker.handler(messageHandler, config)
404
+
405
+ expect(messageHandler).toHaveBeenCalledWith(JSON.stringify({ cli: 'data' }))
406
+ })
407
+
408
+ it('should throw when cli mode has no environment variable', async () => {
409
+ const messageHandler = jest.fn()
410
+ const config = {
411
+ mode: 'cli' as const,
412
+ }
413
+
414
+ await Worker.handler(messageHandler, config)
415
+
416
+ expect(process.exit).toHaveBeenCalledWith(1)
417
+ })
418
+
419
+ it('should exit when mode is unknown', async () => {
420
+ const messageHandler = jest.fn()
421
+ const config = {
422
+ mode: 'unknown' as any,
423
+ }
424
+
425
+ await Worker.handler(messageHandler, config)
426
+
427
+ expect(process.exit).toHaveBeenCalledWith(1)
428
+ })
285
429
  })
286
430
 
287
431
  describe('logger', () => {