This commit is contained in:
@@ -1,233 +0,0 @@
|
|||||||
# tests/test_op25_controller.py
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import patch, mock_open, MagicMock
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
from app.op25_controller import router
|
|
||||||
from fastapi import FastAPI
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Initialize the FastAPI app with the router for testing
|
|
||||||
app = FastAPI()
|
|
||||||
app.include_router(router, prefix="/op25")
|
|
||||||
client = TestClient(app)
|
|
||||||
|
|
||||||
# Example input and expected outputs
|
|
||||||
example_input_json = {
|
|
||||||
"type": "P25",
|
|
||||||
"systemName": "MTA",
|
|
||||||
"channels": [
|
|
||||||
"770.15625",
|
|
||||||
"770.43125",
|
|
||||||
"773.29375",
|
|
||||||
"773.84375",
|
|
||||||
"774.30625",
|
|
||||||
"123.32132"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
{
|
|
||||||
"talkgroup": "abc",
|
|
||||||
"tagDec": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"talkgroup": "deef",
|
|
||||||
"tagDec": 123
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"whitelist": [
|
|
||||||
123,
|
|
||||||
321,
|
|
||||||
456,
|
|
||||||
654,
|
|
||||||
888
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
expected_active_config_json = {
|
|
||||||
"channels": [
|
|
||||||
{
|
|
||||||
"name": "MTA",
|
|
||||||
"device": "sdr",
|
|
||||||
"trunking_sysname": "MTA",
|
|
||||||
"enable_analog": "off",
|
|
||||||
"demod_type": "cqpsk",
|
|
||||||
"cqpsk_tracking": True,
|
|
||||||
"filter_type": "rc",
|
|
||||||
"tracking_threshold": 120,
|
|
||||||
"tracking_feedback": 0.75,
|
|
||||||
"destination": "udp://localhost:23456",
|
|
||||||
"excess_bw": 0.2,
|
|
||||||
"if_rate": 24000,
|
|
||||||
"plot": "",
|
|
||||||
"symbol_rate": 4800,
|
|
||||||
"blacklist": "",
|
|
||||||
"whitelist": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"devices": [
|
|
||||||
{
|
|
||||||
"args": "rtl=0",
|
|
||||||
"gains": "lna:39",
|
|
||||||
"gain_mode": False,
|
|
||||||
"name": "sdr",
|
|
||||||
"offset": 0,
|
|
||||||
"ppm": 0.0,
|
|
||||||
"rate": 1920000,
|
|
||||||
"usable_bw_pct": 0.85,
|
|
||||||
"tunable": True
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"trunking": {
|
|
||||||
"module": "tk_p25.py",
|
|
||||||
"chans": [
|
|
||||||
{
|
|
||||||
"sysname": "MTA",
|
|
||||||
"control_channel_list": "770.15625,770.43125,773.29375,773.84375,774.30625,123.32132",
|
|
||||||
"tagsFile": "/configs/active.cfg.tags.tsv",
|
|
||||||
"whitelist": "/configs/active.cfg.whitelist.tsv",
|
|
||||||
"nac": "",
|
|
||||||
"wacn": "",
|
|
||||||
"tdma_cc": False,
|
|
||||||
"crypt_behavior": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"audio": {
|
|
||||||
"module": "sockaudio.py",
|
|
||||||
"instances": [
|
|
||||||
{
|
|
||||||
"instance_name": "audio0",
|
|
||||||
"device_name": "pulse",
|
|
||||||
"udp_port": 23456,
|
|
||||||
"audio_gain": 2.5,
|
|
||||||
"number_channels": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"terminal": {
|
|
||||||
"module": "terminal.py",
|
|
||||||
"terminal_type": "http:0.0.0.0:8081",
|
|
||||||
"terminal_timeout": 5.0,
|
|
||||||
"curses_plot_interval": 0.2,
|
|
||||||
"http_plot_interval": 1.0,
|
|
||||||
"http_plot_directory": "../www/images",
|
|
||||||
"tuning_step_large": 1200,
|
|
||||||
"tuning_step_small": 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expected_tags_tsv = "abc\t1\ndeef\t123\n"
|
|
||||||
expected_whitelist_tsv = "123\t\n321\t\n456\t\n654\t\n888\t\n"
|
|
||||||
|
|
||||||
# Mock data for subprocess.Popen
|
|
||||||
mock_popen = MagicMock()
|
|
||||||
mock_process = MagicMock()
|
|
||||||
mock_popen.return_value = mock_process
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_subprocess_popen():
|
|
||||||
with patch("app.op25_controller.subprocess.Popen", return_value=mock_process) as mock_popen_patched:
|
|
||||||
yield mock_popen_patched
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_os_killpg():
|
|
||||||
with patch("app.op25_controller.os.killpg") as mock_killpg_patched:
|
|
||||||
yield mock_killpg_patched
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_open_functions():
|
|
||||||
with patch("builtins.open", mock_open()) as mock_file:
|
|
||||||
yield mock_file
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_json_dump():
|
|
||||||
with patch("app.op25_controller.json.dump") as mock_json_dump_patched:
|
|
||||||
yield mock_json_dump_patched
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_csv_writer():
|
|
||||||
with patch("app.op25_controller.csv.writer") as mock_csv_writer_patched:
|
|
||||||
yield mock_csv_writer_patched
|
|
||||||
|
|
||||||
def test_generate_config_p25(
|
|
||||||
mock_open_functions, mock_json_dump, mock_csv_writer
|
|
||||||
):
|
|
||||||
# Prepare the response of csv.writer
|
|
||||||
mock_writer_instance = MagicMock()
|
|
||||||
mock_csv_writer.return_value = mock_writer_instance
|
|
||||||
|
|
||||||
response = client.post("/op25/generate-config", json=example_input_json)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"message": "Config exported to '/configs/active.cfg.json'"}
|
|
||||||
|
|
||||||
# Check that json.dump was called with the correct data
|
|
||||||
mock_json_dump.assert_called_once()
|
|
||||||
args, kwargs = mock_json_dump.call_args
|
|
||||||
config_written = args[0]
|
|
||||||
assert config_written == expected_active_config_json
|
|
||||||
assert kwargs["fp"].name == '/configs/active.cfg.json'
|
|
||||||
|
|
||||||
# Check that tags were written correctly
|
|
||||||
expected_tags = [
|
|
||||||
["abc", 1],
|
|
||||||
["deef", 123]
|
|
||||||
]
|
|
||||||
mock_writer_instance.writerow.assert_any_call(["abc", 1])
|
|
||||||
mock_writer_instance.writerow.assert_any_call(["deef", 123])
|
|
||||||
|
|
||||||
# Similarly, check whitelist writing
|
|
||||||
# Since both tags and whitelist are written, ensure writerow for whitelist is also called
|
|
||||||
whitelist_calls = [
|
|
||||||
patch.call([123]),
|
|
||||||
patch.call([321]),
|
|
||||||
patch.call([456]),
|
|
||||||
patch.call([654]),
|
|
||||||
patch.call([888])
|
|
||||||
]
|
|
||||||
# However, since csv.writer is mocked, and both write operations use the same mock,
|
|
||||||
# it's difficult to differentiate between tags and whitelist writes unless separated.
|
|
||||||
# A better approach would be to separate the file paths.
|
|
||||||
# For simplicity, assume that the writer is called twice: once for tags, once for whitelist
|
|
||||||
|
|
||||||
def test_start_op25(mock_subprocess_popen):
|
|
||||||
# Start OP25 when it's not running
|
|
||||||
response = client.post("/op25/start")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"status": "OP25 started"}
|
|
||||||
mock_subprocess_popen.assert_called_once_with(
|
|
||||||
"/op25/op25/gr-op25_repeater/apps/run_multi-rx_service.sh",
|
|
||||||
shell=True,
|
|
||||||
preexec_fn=ANY,
|
|
||||||
cwd="/op25/op25_gr-repeater/apps/"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Start OP25 again when it's already running
|
|
||||||
response = client.post("/op25/start")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"status": "OP25 already running"}
|
|
||||||
|
|
||||||
def test_stop_op25(mock_subprocess_popen, mock_os_killpg, mock_process):
|
|
||||||
# Ensure OP25 is running first
|
|
||||||
with patch("app.op25_controller.op25_process", mock_process):
|
|
||||||
response = client.post("/op25/stop")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"status": "OP25 stopped"}
|
|
||||||
mock_os_killpg.assert_called_once()
|
|
||||||
|
|
||||||
# Stop OP25 when it's not running
|
|
||||||
with patch("app.op25_controller.op25_process", None):
|
|
||||||
response = client.post("/op25/stop")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"status": "OP25 is not running"}
|
|
||||||
|
|
||||||
def test_get_status():
|
|
||||||
# When OP25 is not running
|
|
||||||
response = client.get("/op25/status")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"status": "stopped"}
|
|
||||||
|
|
||||||
# When OP25 is running
|
|
||||||
with patch("app.op25_controller.op25_process", MagicMock()):
|
|
||||||
response = client.get("/op25/status")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json() == {"status": "running"}
|
|
||||||
Reference in New Issue
Block a user