import pytest import json import subprocess from unittest.mock import MagicMock, patch from flask import Flask from routes.bluetooth import bluetooth_bp, classify_bt_device, detect_tracker @pytest.fixture(autouse=True) def mock_app_module(mocker): mock_app = mocker.patch("routes.bluetooth.app_module") mock_app.bt_devices = {} mock_app.bt_beacons = {} mock_app.bt_services = {} mock_app.bt_queue = MagicMock() mock_app.bt_lock = MagicMock() mock_app.bt_process = None mock_app.bt_interface = "hci0" return mock_app @pytest.fixture def app(): app = Flask(__name__) app.register_blueprint(bluetooth_bp) return app @pytest.fixture def client(app): return app.test_client() def test_classify_bt_device_by_name(): """Test classification based on common naming patterns.""" assert classify_bt_device("Sony WH-1000XM4", None, None) == "audio" assert classify_bt_device("iPhone 15", None, None) == "phone" assert classify_bt_device("Garmin Fenix", None, None) == "wearable" assert classify_bt_device("Microsoft Mouse", None, None) == "input" assert classify_bt_device("AirTag", None, None) == "tracker" assert classify_bt_device("Generic Device", None, None) == "other" def test_classify_bt_device_by_class(): """Test classification based on Bluetooth Class of Device (CoD).""" assert classify_bt_device(None, 0x0100, None) == "computer" assert classify_bt_device(None, 0x0200, None) == "phone" assert classify_bt_device(None, 0x0400, None) == "audio" def test_detect_tracker_by_mac(): """Test tracker detection using MAC OUI prefixes.""" # Assuming 'FF:FF:FF' is a mock prefix in patterns for testing with patch("routes.bluetooth.TILE_PREFIXES", ["FF:FF"]): result = detect_tracker("FF:FF:00:11:22:33", "Unknown") assert result["type"] == "tile" def test_detect_tracker_by_name(): """Test tracker detection using name strings.""" result = detect_tracker("00:11:22:33:44:55", "My AirTag") assert result["type"] == "airtag" assert result["risk"] == "high" # --- Route Tests --- def test_get_interfaces_route(client, mocker): """Test the /interfaces endpoint with mocked system output.""" mock_run = mocker.patch("subprocess.run") # Mocking hciconfig output for a Linux system mock_run.return_value = MagicMock( stdout="hci0:\tType: Primary Bus: USB\n\tBD Address: 00:11:22:33:44:55 ACL MTU: 1021:8 SCO MTU: 64:1\n\tUP RUNNING\n" ) mocker.patch("platform.system", return_value="Linux") mocker.patch("routes.bluetooth.check_tool", return_value=True) response = client.get("/bt/interfaces") data = response.get_json() assert response.status_code == 200 assert data["interfaces"][0]["name"] == "hci0" assert data["interfaces"][0]["status"] == "up" assert data["tools"]["hcitool"] is True def test_stop_scan_route(client, mock_app_module): """Test stopping a running scan process.""" mock_process = MagicMock() mock_app_module.bt_process = mock_process response = client.post("/bt/scan/stop") assert response.status_code == 200 assert response.get_json()["status"] == "stopped" mock_process.terminate.assert_called_once() def test_enum_services_error_no_mac(client): """Test service enumeration validation.""" response = client.post("/bt/enum", json={}) assert response.status_code == 200 assert response.get_json()["status"] == "error" def test_get_devices_route(client, mock_app_module): """Test retrieving the current device list from memory.""" mock_app_module.bt_devices = {"00:11:22:33:44:55": {"mac": "00:11:22:33:44:55", "name": "Test Device"}} response = client.get("/bt/devices") data = response.get_json() assert response.status_code == 200 assert len(data["devices"]) == 1 assert data["devices"][0]["name"] == "Test Device" def test_reload_oui_route(client, mocker): """Test the OUI database reload functionality.""" mocker.patch("routes.bluetooth.load_oui_database", return_value={"001122": "Test Corp"}) response = client.post("/bt/reload-oui") data = response.get_json() assert response.status_code == 200 assert data["status"] == "success" assert data["entries"] > 0