File size: 1,647 Bytes
2276d59
 
 
 
 
 
 
 
 
 
 
 
65fc551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2276d59
 
 
 
 
 
 
 
 
 
65fc551
2276d59
65fc551
 
 
 
 
 
2276d59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { describe, it, expect, vi } from 'vitest';
import { EventEmitter } from 'node:events';
import { spawn } from 'node:child_process';
import { setupStreamListeners } from '../../src/utils/media/stream.util.js';
import { Response } from 'express';
import path from 'node:path';
import { Readable } from 'node:stream';

describe('FFmpeg Process Lifecycle & Sandbox Cleanup', () => {
  it('Should systematically terminate active OS processes when client disconnects abruptly', async () => {
    // spawn FFmpeg
    // loop process
    const fixturePath = path.resolve(
      __dirname,
      '../fixtures/audio/minimal_sine.mp3'
    );
    const dummyProcess = spawn(
      'ffmpeg',
      [
        '-stream_loop',
        '-1', // loop indefinitely
        '-i',
        fixturePath,
        '-f',
        'mp3',
        'pipe:1',
      ],
      { detached: true }
    );

    const pid = dummyProcess.pid;
    expect(pid).toBeGreaterThan(0);

    // mock Response
    const mockRes = new EventEmitter() as unknown as Response;
    mockRes.writableEnded = false;
    mockRes.write = vi.fn();
    mockRes.end = vi.fn();

    const totalBytes = { value: 0 };

    // bind hooks
    setupStreamListeners(
      dummyProcess.stdout as unknown as Readable,
      mockRes,
      'test-client-id',
      totalBytes
    );

    // wait init
    await new Promise((resolve) => setTimeout(resolve, 50));

    // trigger kill
    dummyProcess.kill('SIGKILL');

    // wait cleanup
    await new Promise((resolve) => setTimeout(resolve, 150));

    // verify death
    // expect ESRCH
    expect(() => process.kill(pid as number, 0)).toThrow();
  });
});