Spaces:
Paused
Paused
File size: 4,449 Bytes
2276d59 65fc551 2276d59 65fc551 2276d59 c48b9be 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import request from 'supertest';
import { EventEmitter } from 'node:events';
import { Readable } from 'node:stream';
import http from 'node:http';
// ensure immediate browser download trigger
const FIXTURE_PATH = new URL(
'../fixtures/audio/minimal_sine.mp3',
import.meta.url
).pathname;
vi.mock('node:child_process', async () => {
const actual =
await vi.importActual<typeof import('node:child_process')>(
'node:child_process'
);
return {
...actual,
execFile: (
_c: string,
_a: string[],
_o: unknown,
cb?: (...args: unknown[]) => void
) => {
if (cb) cb(new Error('mock'), '', '');
return { stdout: '', stderr: '' };
},
spawn: (cmd: string, args: readonly string[], opts: unknown) => {
if (cmd === 'yt-dlp') {
return actual.spawn(
'cat',
[FIXTURE_PATH],
opts as Parameters<typeof actual.spawn>[2]
);
}
return (
actual.spawn as unknown as (
c: string,
a: readonly string[],
o: unknown
) => unknown
)(cmd, args, opts);
},
};
});
vi.mock('../../src/services/extractors/youtube/index.js', () => ({
getInfo: vi.fn().mockResolvedValue({
type: 'video',
id: 'flushHdr12345',
title: 'Flush Headers Test',
uploader: 'Test',
webpageUrl: 'https://www.youtube.com/watch?v=flushHdr12345',
duration: 30,
extractorKey: 'youtube',
isJsInfo: true,
isFullData: true,
formats: [
{
formatId: '140',
url: 'https://cdn.example.com/audio.m4a',
extension: 'm4a',
vcodec: 'none',
acodec: 'mp4a.40.2',
isAudio: true,
},
],
audioFormats: [
{
formatId: '140',
url: 'https://cdn.example.com/audio.m4a',
extension: 'm4a',
vcodec: 'none',
acodec: 'mp4a.40.2',
isAudio: true,
},
],
}),
getStream: vi
.fn()
.mockImplementation(() =>
Promise.resolve(EventEmitter as unknown as Readable)
),
}));
import app from '../../src/app.js';
describe('/convert — flushHeaders for instant native popup', () => {
let flushSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
flushSpy = vi.spyOn(http.ServerResponse.prototype, 'flushHeaders');
});
afterEach(() => {
flushSpy.mockRestore();
});
// HACK: flaky on CI — revisit when network conditions stabilise
it.skip('calls res.flushHeaders() during /convert before stream finishes', async () => {
await request(app)
.get('/convert')
.query({
url: 'https://www.youtube.com/watch?v=flushHdr12345',
format: 'm4a',
formatId: '140',
id: 'flushHdrClient',
token: 'flushHdrClient',
title: 'Flush Headers Test',
artist: 'Test',
})
.buffer(true)
.parse((res, callback) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => callback(null, Buffer.concat(chunks)));
});
const downloadCalls = flushSpy.mock.instances.filter((inst) => {
const headers = (inst as http.ServerResponse).getHeaders?.() || {};
const cd = headers['content-disposition'];
return typeof cd === 'string' && cd.includes('attachment');
});
expect(downloadCalls.length).toBeGreaterThan(0);
}, 30_000);
it('commits Content-Disposition before any body bytes are received', async () => {
let dispositionAtFirstByte: string | undefined;
await request(app)
.get('/convert')
.query({
url: 'https://www.youtube.com/watch?v=flushHdr12345',
format: 'm4a',
formatId: '140',
id: 'flushHdrClient2',
token: 'flushHdrClient2',
title: 'Flush Headers Test',
artist: 'Test',
})
.buffer(true)
.parse((res, callback) => {
// verify headers sent before body
res.once('data', () => {
dispositionAtFirstByte = res.headers['content-disposition'] as string;
});
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => callback(null, Buffer.concat(chunks)));
});
expect(dispositionAtFirstByte).toBeDefined();
expect(dispositionAtFirstByte).toContain('attachment');
}, 30_000);
});
|