video-proxy.test.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. const assert = require('node:assert/strict');
  2. const http = require('node:http');
  3. const test = require('node:test');
  4. const express = require('express');
  5. const { registerVideoProxyRoutes } = require('./video-proxy');
  6. function startServer(deps) {
  7. const app = express();
  8. registerVideoProxyRoutes(app, {
  9. isSafeRemoteUrl: () => true,
  10. sleep: async () => {},
  11. ...deps
  12. });
  13. app.get('/health', (req, res) => res.json({ status: 'ok' }));
  14. return new Promise(resolve => {
  15. const server = app.listen(0, '127.0.0.1', () => {
  16. resolve({
  17. server,
  18. baseUrl: `http://127.0.0.1:${server.address().port}`
  19. });
  20. });
  21. });
  22. }
  23. function request(url, headers = {}) {
  24. return new Promise((resolve, reject) => {
  25. const req = http.get(url, { headers }, res => {
  26. const chunks = [];
  27. res.on('data', chunk => chunks.push(chunk));
  28. res.on('end', () => resolve({
  29. status: res.statusCode,
  30. body: Buffer.concat(chunks).toString('utf8')
  31. }));
  32. res.on('error', reject);
  33. });
  34. req.on('error', reject);
  35. });
  36. }
  37. test('retries retryable connection errors before streaming', async t => {
  38. let attempts = 0;
  39. const { server, baseUrl } = await startServer({
  40. maxAttempts: 3,
  41. fetchImpl: async () => {
  42. attempts += 1;
  43. if (attempts < 3) {
  44. throw Object.assign(new Error('fetch failed'), {
  45. cause: { code: 'UND_ERR_CONNECT_TIMEOUT' }
  46. });
  47. }
  48. return new Response('video-bytes', {
  49. status: 200,
  50. headers: { 'content-type': 'video/mp4' }
  51. });
  52. }
  53. });
  54. t.after(() => server.close());
  55. const response = await request(`${baseUrl}/api/video-proxy?url=https://example.com/video.mp4`);
  56. assert.equal(attempts, 3);
  57. assert.equal(response.status, 200);
  58. assert.equal(response.body, 'video-bytes');
  59. });
  60. test('keeps serving requests when the upstream stream fails', async t => {
  61. const failingBody = new ReadableStream({
  62. start(controller) {
  63. controller.enqueue(new TextEncoder().encode('partial'));
  64. controller.error(Object.assign(new Error('terminated'), {
  65. cause: { code: 'ECONNRESET' }
  66. }));
  67. }
  68. });
  69. const { server, baseUrl } = await startServer({
  70. fetchImpl: async () => new Response(failingBody, {
  71. status: 200,
  72. headers: { 'content-type': 'video/mp4' }
  73. })
  74. });
  75. t.after(() => server.close());
  76. await request(`${baseUrl}/api/video-proxy?url=https://example.com/video.mp4`).catch(() => {});
  77. const health = await request(`${baseUrl}/health`);
  78. assert.equal(health.status, 200);
  79. assert.deepEqual(JSON.parse(health.body), { status: 'ok' });
  80. });
  81. test('does not retry an upstream HTTP error', async t => {
  82. let attempts = 0;
  83. const { server, baseUrl } = await startServer({
  84. fetchImpl: async () => {
  85. attempts += 1;
  86. return new Response('not found', { status: 404 });
  87. }
  88. });
  89. t.after(() => server.close());
  90. const response = await request(`${baseUrl}/api/video-proxy?url=https://example.com/missing.mp4`);
  91. assert.equal(attempts, 1);
  92. assert.equal(response.status, 404);
  93. });