import os import sys from unittest.mock import MagicMock, mock_open, patch import pytest from flask import Flask sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from routes.wifi import parse_airodump_csv, wifi_bp @pytest.fixture def mock_app_module(mocker): """Mock the app_module imported inside routes.wifi.""" mock = mocker.patch("routes.wifi.app_module") mock.wifi_lock = MagicMock() mock.wifi_process = None mock.wifi_monitor_interface = None mock.wifi_queue = MagicMock() mock.wifi_networks = {} mock_app_module.wifi_clients = {} return mock @pytest.fixture def app(): app = Flask(__name__) app.register_blueprint(wifi_bp) return app @pytest.fixture def client(app): return app.test_client() def test_parse_airodump_csv(mocker): """Test parsing logic for airodump CSV format.""" csv_content = ( "BSSID, First time seen, Last time seen, channel, Speed, Privacy, Cipher, Authentication, Power, # beacons, # IV, LAN IP, ID-length, ESSID, Key\n" "AA:BB:CC:DD:EE:FF, 2023-01-01, 2023-01-01, 6, 54, WPA2, CCMP, PSK, -50, 10, 5, 0.0.0.0, 7, MyWiFi, \n" "\n" "Station MAC, First time seen, Last time seen, Power, # packets, BSSID, Probes\n" "11:22:33:44:55:66, 2023-01-01, 2023-01-01, -60, 20, AA:BB:CC:DD:EE:FF, MyWiFi\n" ) with patch("builtins.open", mock_open(read_data=csv_content)): mocker.patch("routes.wifi.get_manufacturer", return_value="Apple") networks, clients = parse_airodump_csv("dummy.csv") assert "AA:BB:CC:DD:EE:FF" in networks assert networks["AA:BB:CC:DD:EE:FF"]["essid"] == "MyWiFi" assert "11:22:33:44:55:66" in clients assert clients["11:22:33:44:55:66"]["vendor"] == "Apple" ### --- ROUTE TESTS --- ### def test_get_interfaces(client, mocker): """Test the /interfaces endpoint.""" mocker.patch("routes.wifi.detect_wifi_interfaces", return_value=[{"name": "wlan0", "type": "managed"}]) mocker.patch("routes.wifi.check_tool", return_value=True) response = client.get("/wifi/interfaces") data = response.get_json() assert response.status_code == 200 assert len(data["interfaces"]) == 1 assert data["tools"]["airmon"] is True def test_toggle_monitor_start_success(client, mocker): """Test enabling monitor mode via airmon-ng.""" mocker.patch("routes.wifi.validate_network_interface", return_value="wlan0") mocker.patch("routes.wifi.check_tool", return_value=True) mock_run = mocker.patch("routes.wifi.subprocess.run") mock_run.return_value = MagicMock(stdout="enabled on [phy0]wlan0mon", stderr="", returncode=0) with patch("os.path.exists", return_value=True): response = client.post("/wifi/monitor", json={"action": "start", "interface": "wlan0"}) assert response.status_code == 200 assert response.get_json()["status"] == "success" assert response.get_json()["monitor_interface"] == "wlan0mon" def test_start_scan_already_running(client, mock_app_module): """Test that we can't start a scan if one is already active.""" mock_app_module.wifi_process = MagicMock() response = client.post("/wifi/scan/start", json={"interface": "wlan0mon"}) data = response.get_json() assert data["status"] == "error" assert "already running" in data["message"] def test_start_scan_execution(client, mock_app_module, mocker): """Test the full command construction of airodump-ng.""" mock_app_module.wifi_process = None mocker.patch("os.path.exists", return_value=True) mocker.patch("routes.wifi.get_tool_path", return_value="/usr/bin/airodump-ng") mock_popen = mocker.patch("routes.wifi.subprocess.Popen") mock_proc = MagicMock() mock_proc.poll.return_value = None mock_popen.return_value = mock_proc payload = {"interface": "wlan0mon", "channel": 6, "band": "g"} response = client.post("/wifi/scan/start", json=payload) assert response.status_code == 200 assert response.get_json()["status"] == "started" args, _ = mock_popen.call_args cmd = args[0] assert "-c" in cmd and "6" in cmd assert "wlan0mon" in cmd def test_stop_scan(client, mock_app_module): """Test terminating the scanning process.""" mock_proc = MagicMock() mock_app_module.wifi_process = mock_proc response = client.post("/wifi/scan/stop") assert response.status_code == 200 assert response.get_json()["status"] == "stopped" mock_proc.terminate.assert_called_once() def test_send_deauth_success(client, mock_app_module, mocker): """Verify deauth command construction and execution.""" mocker.patch("routes.wifi.check_tool", return_value=True) mocker.patch("routes.wifi.get_tool_path", return_value="/usr/bin/aireplay-ng") mock_run = mocker.patch("routes.wifi.subprocess.run") mock_run.return_value = MagicMock(returncode=0) payload = {"bssid": "AA:BB:CC:DD:EE:FF", "count": 10, "interface": "wlan0mon"} response = client.post("/wifi/deauth", json=payload) assert response.status_code == 200 args, _ = mock_run.call_args cmd = args[0] assert "--deauth" in cmd assert "10" in cmd assert "AA:BB:CC:DD:EE:FF" in cmd ### --- HANDSHAKE TESTS --- ### def test_capture_handshake_start(client, mock_app_module, mocker): """Test starting airodump-ng for handshake capture.""" mock_app_module.wifi_process = None mocker.patch("routes.wifi.get_tool_path", return_value="/usr/bin/airodump-ng") mock_popen = mocker.patch("routes.wifi.subprocess.Popen") payload = {"bssid": "AA:BB:CC:DD:EE:FF", "channel": "6", "interface": "wlan0mon"} response = client.post("/wifi/handshake/capture", json=payload) assert response.status_code == 200 assert "capture_file" in response.get_json() assert mock_popen.called def test_check_handshake_status_found(client, mocker): """Verify detection of 'KEY FOUND' in aircrack output.""" mocker.patch("os.path.exists", return_value=True) mocker.patch("os.path.getsize", return_value=1024) mocker.patch("routes.wifi.get_tool_path", return_value="aircrack-ng") mock_run = mocker.patch("routes.wifi.subprocess.run") mock_run.return_value = MagicMock(stdout="WPA (1 handshake)", stderr="", returncode=0) payload = {"file": "/tmp/intercept_handshake_test.cap", "bssid": "AA:BB:CC:DD:EE:FF"} response = client.post("/wifi/handshake/status", json=payload) assert response.get_json()["handshake_found"] is True ### --- PMKID TESTS --- ### def test_capture_pmkid_path_traversal_prevention(client): """Ensure the status check rejects invalid paths.""" payload = {"file": "/etc/passwd"} # Malicious path response = client.post("/wifi/pmkid/status", json=payload) assert response.status_code == 400 assert response.get_json()["status"] == "error" assert "Invalid capture file path" in response.get_json()["message"] ### --- CRACKING TESTS --- ### def test_crack_handshake_success(client, mocker): """Test successful password extraction using Regex.""" mocker.patch("os.path.exists", return_value=True) mocker.patch("routes.wifi.get_tool_path", return_value="aircrack-ng") mock_run = mocker.patch("routes.wifi.subprocess.run") # Simulate the actual aircrack-ng success output mock_run.return_value = MagicMock(stdout="KEY FOUND! [ secret123 ]", stderr="", returncode=0) payload = { "capture_file": "/tmp/intercept_handshake_test.cap", "wordlist": "/home/user/passwords.txt", "bssid": "AA:BB:CC:DD:EE:FF", } response = client.post("/wifi/handshake/crack", json=payload) data = response.get_json() assert data["status"] == "success" assert data["password"] == "secret123" ### --- DATA FETCHING TESTS --- ### def test_get_wifi_networks(client, mock_app_module): """Test that the networks endpoint correctly formats internal data.""" mock_app_module.wifi_networks = {"AA:BB:CC:DD:EE:FF": {"essid": "Home-WiFi", "bssid": "AA:BB:CC:DD:EE:FF"}} mock_app_module.wifi_handshakes = ["AA:BB:CC:DD:EE:FF"] response = client.get("/wifi/networks") data = response.get_json() assert len(data["networks"]) == 1 assert data["networks"][0]["essid"] == "Home-WiFi" assert "AA:BB:CC:DD:EE:FF" in data["handshakes"]