Files
intercept/tests/test_subghz_routes.py
T
James Smith 96172ca593 style: apply ruff-format to entire codebase
First-time run of ruff-format via pre-commit hook normalises quote
style, trailing commas, and whitespace across 188 Python files.
No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 14:48:11 +01:00

481 lines
18 KiB
Python

"""Tests for SubGHz transceiver routes."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from utils.subghz import SubGhzCapture
@pytest.fixture
def auth_client(client):
"""Client with logged-in session."""
with client.session_transaction() as sess:
sess["logged_in"] = True
return client
class TestSubGhzRoutes:
"""Tests for /subghz/ endpoints."""
def test_get_status(self, client, auth_client):
"""GET /subghz/status returns manager status."""
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.get_status.return_value = {
"mode": "idle",
"hackrf_available": True,
"rtl433_available": True,
"sweep_available": True,
}
mock_get.return_value = mock_mgr
response = auth_client.get("/subghz/status")
assert response.status_code == 200
data = response.get_json()
assert data["mode"] == "idle"
assert data["hackrf_available"] is True
def test_get_presets(self, client, auth_client):
"""GET /subghz/presets returns frequency presets."""
response = auth_client.get("/subghz/presets")
assert response.status_code == 200
data = response.get_json()
assert "presets" in data
assert "433.92 MHz" in data["presets"]
assert "sample_rates" in data
# ------ RECEIVE ------
def test_start_receive_success(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.start_receive.return_value = {
"status": "started",
"frequency_hz": 433920000,
"sample_rate": 2000000,
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/receive/start",
json={
"frequency_hz": 433920000,
"sample_rate": 2000000,
"lna_gain": 32,
"vga_gain": 20,
},
)
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "started"
def test_start_receive_missing_frequency(self, client, auth_client):
response = auth_client.post("/subghz/receive/start", json={})
assert response.status_code == 400
data = response.get_json()
assert data["status"] == "error"
def test_start_receive_invalid_frequency(self, client, auth_client):
response = auth_client.post(
"/subghz/receive/start",
json={
"frequency_hz": "not_a_number",
},
)
assert response.status_code == 400
def test_stop_receive(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.stop_receive.return_value = {"status": "stopped"}
mock_get.return_value = mock_mgr
response = auth_client.post("/subghz/receive/stop")
assert response.status_code == 200
def test_start_receive_trigger_params(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.start_receive.return_value = {"status": "started"}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/receive/start",
json={
"frequency_hz": 433920000,
"trigger_enabled": True,
"trigger_pre_ms": 400,
"trigger_post_ms": 900,
},
)
assert response.status_code == 200
kwargs = mock_mgr.start_receive.call_args.kwargs
assert kwargs["trigger_enabled"] is True
assert kwargs["trigger_pre_ms"] == 400
assert kwargs["trigger_post_ms"] == 900
# ------ DECODE ------
def test_start_decode_success(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.start_decode.return_value = {
"status": "started",
"frequency_hz": 433920000,
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/decode/start",
json={
"frequency_hz": 433920000,
},
)
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "started"
mock_mgr.start_decode.assert_called_once()
kwargs = mock_mgr.start_decode.call_args.kwargs
assert kwargs["decode_profile"] == "weather"
def test_start_decode_profile_all(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.start_decode.return_value = {
"status": "started",
"frequency_hz": 433920000,
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/decode/start",
json={
"frequency_hz": 433920000,
"decode_profile": "all",
},
)
assert response.status_code == 200
kwargs = mock_mgr.start_decode.call_args.kwargs
assert kwargs["decode_profile"] == "all"
def test_start_decode_missing_freq(self, client, auth_client):
response = auth_client.post("/subghz/decode/start", json={})
assert response.status_code == 400
def test_stop_decode(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.stop_decode.return_value = {"status": "stopped"}
mock_get.return_value = mock_mgr
response = auth_client.post("/subghz/decode/stop")
assert response.status_code == 200
# ------ TRANSMIT ------
def test_transmit_missing_capture_id(self, client, auth_client):
response = auth_client.post("/subghz/transmit", json={})
assert response.status_code == 400
data = response.get_json()
assert "capture_id is required" in data["message"]
def test_transmit_invalid_capture_id(self, client, auth_client):
response = auth_client.post(
"/subghz/transmit",
json={
"capture_id": "../../../etc/passwd",
},
)
assert response.status_code == 400
data = response.get_json()
assert "Invalid" in data["message"]
def test_transmit_success(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.transmit.return_value = {
"status": "transmitting",
"capture_id": "abc123",
"frequency_hz": 433920000,
"max_duration": 10,
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/transmit",
json={
"capture_id": "abc123",
"tx_gain": 20,
"max_duration": 10,
},
)
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "transmitting"
kwargs = mock_mgr.transmit.call_args.kwargs
assert kwargs["start_seconds"] is None
assert kwargs["duration_seconds"] is None
def test_transmit_segment_params(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.transmit.return_value = {
"status": "transmitting",
"capture_id": "abc123",
"frequency_hz": 433920000,
"max_duration": 10,
"segment": {"start_seconds": 0.1, "duration_seconds": 0.4},
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/transmit",
json={
"capture_id": "abc123",
"tx_gain": 20,
"max_duration": 10,
"start_seconds": 0.1,
"duration_seconds": 0.4,
},
)
assert response.status_code == 200
kwargs = mock_mgr.transmit.call_args.kwargs
assert kwargs["start_seconds"] == 0.1
assert kwargs["duration_seconds"] == 0.4
def test_transmit_invalid_segment_param(self, client, auth_client):
response = auth_client.post(
"/subghz/transmit",
json={
"capture_id": "abc123",
"start_seconds": "not-a-number",
},
)
assert response.status_code == 400
def test_stop_transmit(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.stop_transmit.return_value = {"status": "stopped"}
mock_get.return_value = mock_mgr
response = auth_client.post("/subghz/transmit/stop")
assert response.status_code == 200
# ------ SWEEP ------
def test_start_sweep_success(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.start_sweep.return_value = {
"status": "started",
"freq_start_mhz": 300,
"freq_end_mhz": 928,
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/sweep/start",
json={
"freq_start_mhz": 300,
"freq_end_mhz": 928,
},
)
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "started"
def test_start_sweep_invalid_range(self, client, auth_client):
response = auth_client.post(
"/subghz/sweep/start",
json={
"freq_start_mhz": 928,
"freq_end_mhz": 300, # start > end
},
)
assert response.status_code == 400
def test_stop_sweep(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.stop_sweep.return_value = {"status": "stopped"}
mock_get.return_value = mock_mgr
response = auth_client.post("/subghz/sweep/stop")
assert response.status_code == 200
# ------ CAPTURES ------
def test_list_captures_empty(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.list_captures.return_value = []
mock_get.return_value = mock_mgr
response = auth_client.get("/subghz/captures")
assert response.status_code == 200
data = response.get_json()
assert data["count"] == 0
assert data["captures"] == []
def test_list_captures_with_data(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
cap = SubGhzCapture(
capture_id="cap1",
filename="test.iq",
frequency_hz=433920000,
sample_rate=2000000,
lna_gain=32,
vga_gain=20,
timestamp="2026-01-01T00:00:00Z",
)
mock_mgr.list_captures.return_value = [cap]
mock_get.return_value = mock_mgr
response = auth_client.get("/subghz/captures")
assert response.status_code == 200
data = response.get_json()
assert data["count"] == 1
assert data["captures"][0]["id"] == "cap1"
def test_get_capture(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
cap = SubGhzCapture(
capture_id="cap2",
filename="test2.iq",
frequency_hz=315000000,
sample_rate=2000000,
lna_gain=32,
vga_gain=20,
timestamp="2026-01-01T00:00:00Z",
)
mock_mgr.get_capture.return_value = cap
mock_get.return_value = mock_mgr
response = auth_client.get("/subghz/captures/cap2")
assert response.status_code == 200
data = response.get_json()
assert data["capture"]["frequency_hz"] == 315000000
def test_get_capture_not_found(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.get_capture.return_value = None
mock_get.return_value = mock_mgr
response = auth_client.get("/subghz/captures/nonexistent")
assert response.status_code == 404
def test_get_capture_invalid_id(self, client, auth_client):
response = auth_client.get("/subghz/captures/bad-id!")
assert response.status_code == 400
def test_delete_capture(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.delete_capture.return_value = True
mock_get.return_value = mock_mgr
response = auth_client.delete("/subghz/captures/cap1")
assert response.status_code == 200
data = response.get_json()
assert data["status"] == "deleted"
def test_trim_capture_success(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.trim_capture.return_value = {
"status": "ok",
"capture": {
"id": "trim_new",
"filename": "trimmed.iq",
"frequency_hz": 433920000,
"sample_rate": 2000000,
},
}
mock_get.return_value = mock_mgr
response = auth_client.post(
"/subghz/captures/cap1/trim",
json={
"start_seconds": 0.1,
"duration_seconds": 0.3,
},
)
assert response.status_code == 200
kwargs = mock_mgr.trim_capture.call_args.kwargs
assert kwargs["capture_id"] == "cap1"
assert kwargs["start_seconds"] == 0.1
assert kwargs["duration_seconds"] == 0.3
def test_trim_capture_invalid_param(self, client, auth_client):
response = auth_client.post(
"/subghz/captures/cap1/trim",
json={
"start_seconds": "bad",
},
)
assert response.status_code == 400
def test_delete_capture_not_found(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.delete_capture.return_value = False
mock_get.return_value = mock_mgr
response = auth_client.delete("/subghz/captures/nonexistent")
assert response.status_code == 404
def test_update_capture_label(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.update_capture_label.return_value = True
mock_get.return_value = mock_mgr
response = auth_client.patch(
"/subghz/captures/cap1",
json={
"label": "Garage Remote",
},
)
assert response.status_code == 200
data = response.get_json()
assert data["label"] == "Garage Remote"
def test_update_capture_label_too_long(self, client, auth_client):
response = auth_client.patch(
"/subghz/captures/cap1",
json={
"label": "x" * 200,
},
)
assert response.status_code == 400
def test_update_capture_not_found(self, client, auth_client):
with patch("routes.subghz.get_subghz_manager") as mock_get:
mock_mgr = MagicMock()
mock_mgr.update_capture_label.return_value = False
mock_get.return_value = mock_mgr
response = auth_client.patch(
"/subghz/captures/nonexistent",
json={
"label": "test",
},
)
assert response.status_code == 404
# ------ SSE STREAM ------
def test_stream_endpoint(self, client, auth_client):
"""GET /subghz/stream returns SSE response."""
with patch("routes.subghz.sse_stream", return_value=iter([])):
response = auth_client.get("/subghz/stream")
assert response.status_code == 200
assert response.content_type.startswith("text/event-stream")