external_camera.py 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. """Stand-ins for the ffmpeg subprocess the external-camera paths spawn.
  2. Both RTSP paths in ``backend.app.services.external_camera`` build an argv and
  3. hand it to ``asyncio.create_subprocess_exec``. Tests that care about *what we
  4. asked ffmpeg to do* — the SSRF guards, the probe settings — need to see that
  5. argv without an ffmpeg binary being involved, so these patch the lookup and the
  6. spawn and record the call.
  7. """
  8. import asyncio
  9. from unittest.mock import AsyncMock, MagicMock, patch
  10. # What ffmpeg logs once an RTSP input is open; the RTSP paths read no frame
  11. # before it.
  12. INPUT_OPEN_LOG = b"Input #0, rtsp, from 'rtsp://192.168.1.50:554/live':\n"
  13. def stream_of(data: bytes) -> asyncio.StreamReader:
  14. reader = asyncio.StreamReader()
  15. reader.feed_data(data)
  16. reader.feed_eof()
  17. return reader
  18. def fake_ffmpeg():
  19. """Pretend ffmpeg is installed, so the paths get as far as building argv."""
  20. return patch("backend.app.services.external_camera.get_ffmpeg_path", return_value="/usr/bin/ffmpeg")
  21. def spawn_spy(returncode: int | None = 0, stdout: bytes = b"\xff\xd8" + b"\x00" * 200, stderr: bytes = INPUT_OPEN_LOG):
  22. """Stand in for the ffmpeg subprocess, recording the argv it was handed.
  23. The streaming path reads until EOF, so stdout.read returns b"" and the
  24. generator finishes immediately — these tests are about whether ffmpeg was
  25. launched and with what, not about frame extraction.
  26. """
  27. process = MagicMock()
  28. process.returncode = returncode
  29. process.communicate = AsyncMock(return_value=(stdout, b""))
  30. process.stdout.read = AsyncMock(return_value=b"")
  31. process.stderr = stream_of(stderr)
  32. process.wait = AsyncMock(return_value=returncode)
  33. process.kill = MagicMock()
  34. process.terminate = MagicMock()
  35. return patch(
  36. "backend.app.services.external_camera.asyncio.create_subprocess_exec",
  37. new=AsyncMock(return_value=process),
  38. )