@quatrain/worker 1.2.4 → 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.
- package/dist/FileSystem.js +3 -2
- package/dist/Worker.test.js +112 -1
- package/package.json +3 -3
- package/src/FileSystem.ts +3 -2
- package/src/Worker.test.ts +148 -1
package/dist/FileSystem.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
package/dist/Worker.test.js
CHANGED
|
@@ -8,7 +8,19 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
8
8
|
const node_child_process_1 = require("node:child_process");
|
|
9
9
|
// Mock dependencies
|
|
10
10
|
jest.mock('axios');
|
|
11
|
-
jest.mock('child_process');
|
|
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 () => {
|
|
@@ -153,6 +184,7 @@ describe('Worker', () => {
|
|
|
153
184
|
await expect(Worker_1.Worker.execPromise('ls', ['-la'], '/tmp')).resolves.toBeUndefined();
|
|
154
185
|
expect(mockedSpawn).toHaveBeenCalledWith('ls', ['-la'], {
|
|
155
186
|
cwd: '/tmp',
|
|
187
|
+
shell: false,
|
|
156
188
|
});
|
|
157
189
|
});
|
|
158
190
|
it('should reject on command failure', async () => {
|
|
@@ -191,6 +223,7 @@ describe('Worker', () => {
|
|
|
191
223
|
await Worker_1.Worker.execPromise('echo', ['test']);
|
|
192
224
|
expect(mockedSpawn).toHaveBeenCalledWith('echo', ['test'], {
|
|
193
225
|
cwd: process.cwd(),
|
|
226
|
+
shell: false,
|
|
194
227
|
});
|
|
195
228
|
});
|
|
196
229
|
it('should pass arguments to command', async () => {
|
|
@@ -208,8 +241,86 @@ describe('Worker', () => {
|
|
|
208
241
|
await Worker_1.Worker.execPromise('command', args, '/custom/path');
|
|
209
242
|
expect(mockedSpawn).toHaveBeenCalledWith('command', args, {
|
|
210
243
|
cwd: '/custom/path',
|
|
244
|
+
shell: false,
|
|
245
|
+
});
|
|
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,
|
|
211
289
|
});
|
|
212
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
|
+
});
|
|
213
324
|
});
|
|
214
325
|
describe('logger', () => {
|
|
215
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.
|
|
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,9 +34,9 @@
|
|
|
34
34
|
"typescript": "^5.1.5"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@quatrain/core": "^1.2.
|
|
37
|
+
"@quatrain/core": "^1.2.14",
|
|
38
38
|
"@quatrain/queue": "^1.2.3",
|
|
39
|
-
"@quatrain/storage": "^1.2.
|
|
39
|
+
"@quatrain/storage": "^1.2.8",
|
|
40
40
|
"axios": "^1.7.7",
|
|
41
41
|
"fluent-ffmpeg": "^2.1.2",
|
|
42
42
|
"fs-extra": "^11.2.0",
|
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
|
-
|
|
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
|
|
package/src/Worker.test.ts
CHANGED
|
@@ -4,7 +4,19 @@ import { spawn } from 'node:child_process'
|
|
|
4
4
|
|
|
5
5
|
// Mock dependencies
|
|
6
6
|
jest.mock('axios')
|
|
7
|
-
jest.mock('child_process')
|
|
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', () => {
|
|
@@ -209,6 +250,7 @@ describe('Worker', () => {
|
|
|
209
250
|
|
|
210
251
|
expect(mockedSpawn).toHaveBeenCalledWith('ls', ['-la'], {
|
|
211
252
|
cwd: '/tmp',
|
|
253
|
+
shell: false,
|
|
212
254
|
})
|
|
213
255
|
})
|
|
214
256
|
|
|
@@ -256,6 +298,7 @@ describe('Worker', () => {
|
|
|
256
298
|
|
|
257
299
|
expect(mockedSpawn).toHaveBeenCalledWith('echo', ['test'], {
|
|
258
300
|
cwd: process.cwd(),
|
|
301
|
+
shell: false,
|
|
259
302
|
})
|
|
260
303
|
})
|
|
261
304
|
|
|
@@ -277,7 +320,111 @@ describe('Worker', () => {
|
|
|
277
320
|
|
|
278
321
|
expect(mockedSpawn).toHaveBeenCalledWith('command', args, {
|
|
279
322
|
cwd: '/custom/path',
|
|
323
|
+
shell: false,
|
|
324
|
+
})
|
|
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')
|
|
280
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)
|
|
281
428
|
})
|
|
282
429
|
})
|
|
283
430
|
|