mirror of
https://github.com/smittix/intercept.git
synced 2026-06-10 23:13:31 -07:00
v2.26.0: fix SSE fanout crash and branded logo FOUC
- Fix SSE fanout thread AttributeError when source queue is None during interpreter shutdown by snapshotting to local variable with null guard - Fix branded "i" logo rendering oversized on first page load (FOUC) by adding inline width/height to SVG elements across 10 templates - Bump version to 2.26.0 in config.py, pyproject.toml, and CHANGELOG.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+5
-4
@@ -1,9 +1,11 @@
|
||||
"""Pytest configuration and fixtures."""
|
||||
|
||||
import contextlib
|
||||
import sqlite3
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app import app as flask_app
|
||||
from routes import register_blueprints
|
||||
|
||||
@@ -80,9 +82,10 @@ def mock_app_state():
|
||||
|
||||
Provides mock process, queue, and lock objects on the app module.
|
||||
"""
|
||||
import app as app_module
|
||||
import queue
|
||||
|
||||
import app as app_module
|
||||
|
||||
mock_process = MagicMock()
|
||||
mock_process.poll.return_value = None
|
||||
mock_queue = queue.Queue()
|
||||
@@ -107,10 +110,8 @@ def mock_app_state():
|
||||
|
||||
for attr, orig in originals.items():
|
||||
if orig is None:
|
||||
try:
|
||||
with contextlib.suppress(AttributeError):
|
||||
delattr(app_module, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
setattr(app_module, attr, orig)
|
||||
|
||||
|
||||
+5
-7
@@ -12,12 +12,12 @@ Usage:
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -53,7 +53,7 @@ def generate_sensors() -> list[dict]:
|
||||
"""Generate fake 433MHz sensor data."""
|
||||
sensors = []
|
||||
models = ['Acurite-Tower', 'Oregon-THGR122N', 'LaCrosse-TX141W', 'Ambient-F007TH']
|
||||
for i in range(random.randint(2, 5)):
|
||||
for _i in range(random.randint(2, 5)):
|
||||
sensors.append({
|
||||
'time': datetime.now(timezone.utc).isoformat(),
|
||||
'model': random.choice(models),
|
||||
@@ -71,7 +71,7 @@ def generate_wifi_networks() -> list[dict]:
|
||||
networks = []
|
||||
ssids = ['HomeNetwork', 'Linksys', 'NETGEAR', 'xfinitywifi', 'ATT-WIFI', 'CoffeeShop-Guest']
|
||||
for ssid in random.sample(ssids, random.randint(3, 6)):
|
||||
bssid = ':'.join(['%02X' % random.randint(0, 255) for _ in range(6)])
|
||||
bssid = ':'.join([f'{random.randint(0, 255):02X}' for _ in range(6)])
|
||||
networks.append({
|
||||
'ssid': ssid,
|
||||
'bssid': bssid,
|
||||
@@ -89,7 +89,7 @@ def generate_bluetooth_devices() -> list[dict]:
|
||||
devices = []
|
||||
names = ['iPhone', 'Galaxy S21', 'AirPods', 'Tile Tracker', 'Fitbit', 'Unknown']
|
||||
for _ in range(random.randint(2, 8)):
|
||||
mac = ':'.join(['%02X' % random.randint(0, 255) for _ in range(6)])
|
||||
mac = ':'.join([f'{random.randint(0, 255):02X}' for _ in range(6)])
|
||||
devices.append({
|
||||
'address': mac,
|
||||
'name': random.choice(names),
|
||||
@@ -209,9 +209,7 @@ def config():
|
||||
'name': agent_name,
|
||||
'port': request.environ.get('SERVER_PORT', 8021),
|
||||
'push_enabled': False,
|
||||
'modes_enabled': {m: True for m in [
|
||||
'pager', 'sensor', 'adsb', 'ais', 'wifi', 'bluetooth'
|
||||
]}
|
||||
'modes_enabled': dict.fromkeys(['pager', 'sensor', 'adsb', 'ais', 'wifi', 'bluetooth'], True)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@ Requirements:
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
import requests
|
||||
@@ -258,7 +255,7 @@ class SmokeTests:
|
||||
def run_all(self):
|
||||
"""Run all smoke tests."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"BLUETOOTH API SMOKE TESTS")
|
||||
print("BLUETOOTH API SMOKE TESTS")
|
||||
print(f"Target: {self.base_url}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
"""Tests for ACARS message translator."""
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.acars_translator import (
|
||||
ACARS_LABELS,
|
||||
translate_label,
|
||||
classify_message_type,
|
||||
parse_position_report,
|
||||
parse_engine_data,
|
||||
parse_weather_data,
|
||||
parse_oooi,
|
||||
parse_position_report,
|
||||
parse_weather_data,
|
||||
translate_label,
|
||||
translate_message,
|
||||
)
|
||||
|
||||
|
||||
# --- translate_label ---
|
||||
|
||||
class TestTranslateLabel:
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock, patch, PropertyMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
+16
-12
@@ -10,23 +10,27 @@ Tests cover:
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
import tempfile
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from utils.agent_client import (
|
||||
AgentClient, AgentHTTPError, AgentConnectionError, create_client_from_agent
|
||||
)
|
||||
from utils.agent_client import AgentClient, AgentConnectionError, AgentHTTPError, create_client_from_agent
|
||||
from utils.database import (
|
||||
init_db, get_db_path, create_agent, get_agent, get_agent_by_name,
|
||||
list_agents, update_agent, delete_agent, store_push_payload,
|
||||
get_recent_payloads, cleanup_old_payloads
|
||||
create_agent,
|
||||
delete_agent,
|
||||
get_agent,
|
||||
get_agent_by_name,
|
||||
get_recent_payloads,
|
||||
init_db,
|
||||
list_agents,
|
||||
store_push_payload,
|
||||
update_agent,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AgentConfig Tests
|
||||
# =============================================================================
|
||||
@@ -559,8 +563,8 @@ class TestAgentClientIntegration:
|
||||
@pytest.fixture
|
||||
def mock_agent(self):
|
||||
"""Start mock agent server for testing."""
|
||||
|
||||
from tests.mock_agent import app as mock_app
|
||||
import threading
|
||||
|
||||
# Run mock agent in background
|
||||
mock_app.config['TESTING'] = True
|
||||
|
||||
@@ -19,13 +19,14 @@ Skip live tests:
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
|
||||
+11
-15
@@ -10,14 +10,13 @@ Tests cover:
|
||||
- Error handling and edge cases
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import threading
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from datetime import datetime, timezone
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
@@ -34,10 +33,8 @@ def mode_manager():
|
||||
yield manager
|
||||
# Cleanup: stop all modes
|
||||
for mode in list(manager.running_modes.keys()):
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
manager.stop_mode(mode)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -139,14 +136,13 @@ class TestModeLifecycle:
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Mock glob for CSV file detection
|
||||
with patch('glob.glob', return_value=[]):
|
||||
with patch('tempfile.mkdtemp', return_value='/tmp/test'):
|
||||
result = mode_manager.start_mode('wifi', {
|
||||
'interface': 'wlan0',
|
||||
'scan_type': 'quick'
|
||||
})
|
||||
# Quick scan returns data directly
|
||||
assert result['status'] in ['started', 'error', 'success']
|
||||
with patch('glob.glob', return_value=[]), patch('tempfile.mkdtemp', return_value='/tmp/test'):
|
||||
result = mode_manager.start_mode('wifi', {
|
||||
'interface': 'wlan0',
|
||||
'scan_type': 'quick'
|
||||
})
|
||||
# Quick scan returns data directly
|
||||
assert result['status'] in ['started', 'error', 'success']
|
||||
|
||||
def test_bluetooth_mode_lifecycle(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Bluetooth mode should start and stop cleanly."""
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests for main application routes."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_index_page(client):
|
||||
|
||||
@@ -6,7 +6,6 @@ import pytest
|
||||
|
||||
from routes.aprs import parse_aprs_packet
|
||||
|
||||
|
||||
_BASE_PACKET = "N0CALL-9>APRS,TCPIP*:@092345z4903.50N/07201.75W_090/000g005t077"
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
import json
|
||||
import subprocess
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from routes.bluetooth import bluetooth_bp, classify_bt_device, detect_tracker
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"""Unit tests for Bluetooth device aggregation."""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.bluetooth.aggregator import DeviceAggregator
|
||||
from utils.bluetooth.models import BTObservation, BTDeviceAggregate
|
||||
from utils.bluetooth.constants import (
|
||||
MAX_RSSI_SAMPLES,
|
||||
DEVICE_STALE_TIMEOUT as DEVICE_STALE_SECONDS,
|
||||
)
|
||||
from utils.bluetooth.constants import (
|
||||
MAX_RSSI_SAMPLES,
|
||||
)
|
||||
from utils.bluetooth.models import BTObservation
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""API endpoint tests for Bluetooth v2 routes."""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch, PropertyMock
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from routes.bluetooth_v2 import bluetooth_v2_bp
|
||||
from utils.bluetooth.models import BTDeviceAggregate, ScanStatus, SystemCapabilities
|
||||
from utils.bluetooth.models import BTDeviceAggregate, SystemCapabilities
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
"""Unit tests for Bluetooth heuristic detection."""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from utils.bluetooth.heuristics import HeuristicsEngine
|
||||
from utils.bluetooth.models import BTDeviceAggregate
|
||||
import pytest
|
||||
|
||||
from utils.bluetooth.constants import (
|
||||
BEACON_INTERVAL_MAX_VARIANCE as HEURISTIC_BEACON_VARIANCE_THRESHOLD,
|
||||
)
|
||||
from utils.bluetooth.constants import (
|
||||
PERSISTENT_MIN_SEEN_COUNT as HEURISTIC_PERSISTENT_MIN_SEEN,
|
||||
)
|
||||
from utils.bluetooth.constants import (
|
||||
PERSISTENT_WINDOW_SECONDS as HEURISTIC_PERSISTENT_WINDOW_SECONDS,
|
||||
BEACON_INTERVAL_MAX_VARIANCE as HEURISTIC_BEACON_VARIANCE_THRESHOLD,
|
||||
STRONG_RSSI_THRESHOLD as HEURISTIC_STRONG_STABLE_RSSI,
|
||||
)
|
||||
from utils.bluetooth.constants import (
|
||||
STABLE_VARIANCE_THRESHOLD as HEURISTIC_STRONG_STABLE_VARIANCE,
|
||||
)
|
||||
from utils.bluetooth.constants import (
|
||||
STRONG_RSSI_THRESHOLD as HEURISTIC_STRONG_STABLE_RSSI,
|
||||
)
|
||||
from utils.bluetooth.heuristics import HeuristicsEngine
|
||||
from utils.bluetooth.models import BTDeviceAggregate
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -5,21 +5,21 @@ Tests device key stability, EMA smoothing, distance estimation,
|
||||
band classification, and ring buffer functionality.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.bluetooth.device_key import (
|
||||
extract_key_type,
|
||||
generate_device_key,
|
||||
is_randomized_mac,
|
||||
extract_key_type,
|
||||
)
|
||||
from utils.bluetooth.distance import (
|
||||
DistanceEstimator,
|
||||
ProximityBand,
|
||||
RSSI_THRESHOLD_FAR,
|
||||
RSSI_THRESHOLD_IMMEDIATE,
|
||||
RSSI_THRESHOLD_NEAR,
|
||||
RSSI_THRESHOLD_FAR,
|
||||
DistanceEstimator,
|
||||
ProximityBand,
|
||||
)
|
||||
from utils.bluetooth.ring_buffer import RingBuffer
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Tests for configuration module."""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
||||
class TestConfigEnvVars:
|
||||
@@ -9,7 +7,7 @@ class TestConfigEnvVars:
|
||||
|
||||
def test_default_values(self):
|
||||
"""Test that default values are set."""
|
||||
from config import PORT, HOST, DEBUG
|
||||
from config import DEBUG, HOST, PORT
|
||||
|
||||
assert PORT == 5050
|
||||
assert HOST == '0.0.0.0'
|
||||
@@ -22,6 +20,7 @@ class TestConfigEnvVars:
|
||||
|
||||
# Re-import to get new values
|
||||
import importlib
|
||||
|
||||
import config
|
||||
importlib.reload(config)
|
||||
|
||||
@@ -38,6 +37,7 @@ class TestConfigEnvVars:
|
||||
monkeypatch.setenv('INTERCEPT_PORT', 'invalid')
|
||||
|
||||
import importlib
|
||||
|
||||
import config
|
||||
importlib.reload(config)
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ Tests cover:
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
import sys
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
@@ -51,6 +52,7 @@ def setup_db(tmp_path):
|
||||
def app(setup_db):
|
||||
"""Create Flask app with controller blueprint."""
|
||||
from flask import Flask
|
||||
|
||||
from routes.controller import controller_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"""Tests for device correlation engine."""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestDeviceCorrelator:
|
||||
|
||||
+13
-12
@@ -1,11 +1,12 @@
|
||||
"""Tests for database utilities."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# Need to patch DB_PATH before importing database module
|
||||
@pytest.fixture(autouse=True)
|
||||
def temp_db():
|
||||
@@ -17,7 +18,7 @@ def temp_db():
|
||||
with patch('utils.database.DB_PATH', test_db_path), \
|
||||
patch('utils.database.DB_DIR', test_db_dir):
|
||||
# Import after patching
|
||||
from utils.database import init_db, close_db
|
||||
from utils.database import close_db, init_db
|
||||
|
||||
init_db()
|
||||
yield test_db_path
|
||||
@@ -29,14 +30,14 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_set_and_get_string(self, temp_db):
|
||||
"""Test setting and getting string values."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
set_setting('test_key', 'test_value')
|
||||
assert get_setting('test_key') == 'test_value'
|
||||
|
||||
def test_set_and_get_int(self, temp_db):
|
||||
"""Test setting and getting integer values."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
set_setting('int_key', 42)
|
||||
result = get_setting('int_key')
|
||||
@@ -45,7 +46,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_set_and_get_float(self, temp_db):
|
||||
"""Test setting and getting float values."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
set_setting('float_key', 3.14)
|
||||
result = get_setting('float_key')
|
||||
@@ -54,7 +55,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_set_and_get_bool(self, temp_db):
|
||||
"""Test setting and getting boolean values."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
set_setting('bool_true', True)
|
||||
set_setting('bool_false', False)
|
||||
@@ -64,7 +65,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_set_and_get_dict(self, temp_db):
|
||||
"""Test setting and getting dictionary values."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
test_dict = {'name': 'test', 'value': 123, 'nested': {'a': 1}}
|
||||
set_setting('dict_key', test_dict)
|
||||
@@ -75,7 +76,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_set_and_get_list(self, temp_db):
|
||||
"""Test setting and getting list values."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
test_list = [1, 2, 3, 'four', {'five': 5}]
|
||||
set_setting('list_key', test_list)
|
||||
@@ -92,7 +93,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_update_existing_setting(self, temp_db):
|
||||
"""Test updating an existing setting."""
|
||||
from utils.database import set_setting, get_setting
|
||||
from utils.database import get_setting, set_setting
|
||||
|
||||
set_setting('update_key', 'original')
|
||||
assert get_setting('update_key') == 'original'
|
||||
@@ -102,7 +103,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_delete_setting(self, temp_db):
|
||||
"""Test deleting a setting."""
|
||||
from utils.database import set_setting, get_setting, delete_setting
|
||||
from utils.database import delete_setting, get_setting, set_setting
|
||||
|
||||
set_setting('delete_key', 'value')
|
||||
assert get_setting('delete_key') == 'value'
|
||||
@@ -120,7 +121,7 @@ class TestSettingsCRUD:
|
||||
|
||||
def test_get_all_settings(self, temp_db):
|
||||
"""Test getting all settings."""
|
||||
from utils.database import set_setting, get_all_settings
|
||||
from utils.database import get_all_settings, set_setting
|
||||
|
||||
set_setting('key1', 'value1')
|
||||
set_setting('key2', 42)
|
||||
|
||||
@@ -9,17 +9,17 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from utils.constants import (
|
||||
DEAUTH_ALERT_THRESHOLD,
|
||||
DEAUTH_CRITICAL_THRESHOLD,
|
||||
DEAUTH_DETECTION_WINDOW,
|
||||
)
|
||||
from utils.wifi.deauth_detector import (
|
||||
DEAUTH_REASON_CODES,
|
||||
DeauthAlert,
|
||||
DeauthDetector,
|
||||
DeauthPacketInfo,
|
||||
DeauthTracker,
|
||||
DeauthAlert,
|
||||
DEAUTH_REASON_CODES,
|
||||
)
|
||||
from utils.constants import (
|
||||
DEAUTH_DETECTION_WINDOW,
|
||||
DEAUTH_ALERT_THRESHOLD,
|
||||
DEAUTH_CRITICAL_THRESHOLD,
|
||||
)
|
||||
|
||||
|
||||
@@ -515,9 +515,7 @@ class TestDeauthDetectorIntegration:
|
||||
return True
|
||||
if 'Dot11Disas' in str(layer):
|
||||
return False
|
||||
if 'RadioTap' in str(layer):
|
||||
return True
|
||||
return False
|
||||
return 'RadioTap' in str(layer)
|
||||
|
||||
mock_pkt.haslayer = haslayer_side_effect
|
||||
|
||||
|
||||
+24
-48
@@ -1,10 +1,11 @@
|
||||
"""Tests for DSC database operations."""
|
||||
|
||||
import tempfile
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def temp_db():
|
||||
@@ -15,7 +16,7 @@ def temp_db():
|
||||
|
||||
with patch('utils.database.DB_PATH', test_db_path), \
|
||||
patch('utils.database.DB_DIR', test_db_dir):
|
||||
from utils.database import init_db, close_db
|
||||
from utils.database import close_db, init_db
|
||||
|
||||
init_db()
|
||||
yield test_db_path
|
||||
@@ -27,7 +28,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_store_and_get_dsc_alert(self, temp_db):
|
||||
"""Test storing and retrieving a DSC alert."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alert
|
||||
from utils.database import get_dsc_alert, store_dsc_alert
|
||||
|
||||
alert_id = store_dsc_alert(
|
||||
source_mmsi='232123456',
|
||||
@@ -56,7 +57,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_store_minimal_alert(self, temp_db):
|
||||
"""Test storing alert with only required fields."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alert
|
||||
from utils.database import get_dsc_alert, store_dsc_alert
|
||||
|
||||
alert_id = store_dsc_alert(
|
||||
source_mmsi='366000001',
|
||||
@@ -81,7 +82,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alerts_all(self, temp_db):
|
||||
"""Test getting all alerts."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alerts
|
||||
from utils.database import get_dsc_alerts, store_dsc_alert
|
||||
|
||||
store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
store_dsc_alert('366000001', '120', 'URGENCY')
|
||||
@@ -93,7 +94,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alerts_by_category(self, temp_db):
|
||||
"""Test filtering alerts by category."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alerts
|
||||
from utils.database import get_dsc_alerts, store_dsc_alert
|
||||
|
||||
store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
store_dsc_alert('232123457', '100', 'DISTRESS')
|
||||
@@ -108,11 +109,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alerts_by_acknowledged(self, temp_db):
|
||||
"""Test filtering alerts by acknowledgement status."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alerts,
|
||||
acknowledge_dsc_alert
|
||||
)
|
||||
from utils.database import acknowledge_dsc_alert, get_dsc_alerts, store_dsc_alert
|
||||
|
||||
id1 = store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
id2 = store_dsc_alert('366000001', '100', 'DISTRESS')
|
||||
@@ -129,7 +126,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alerts_by_mmsi(self, temp_db):
|
||||
"""Test filtering alerts by source MMSI."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alerts
|
||||
from utils.database import get_dsc_alerts, store_dsc_alert
|
||||
|
||||
store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
store_dsc_alert('232123456', '120', 'URGENCY')
|
||||
@@ -143,7 +140,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alerts_pagination(self, temp_db):
|
||||
"""Test alert pagination."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alerts
|
||||
from utils.database import get_dsc_alerts, store_dsc_alert
|
||||
|
||||
# Create 10 alerts
|
||||
for i in range(10):
|
||||
@@ -164,7 +161,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alerts_order(self, temp_db):
|
||||
"""Test alerts are returned in reverse chronological order."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alerts
|
||||
from utils.database import get_dsc_alerts, store_dsc_alert
|
||||
|
||||
id1 = store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
id2 = store_dsc_alert('366000001', '100', 'DISTRESS')
|
||||
@@ -182,11 +179,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_acknowledge_dsc_alert(self, temp_db):
|
||||
"""Test acknowledging a DSC alert."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alert,
|
||||
acknowledge_dsc_alert
|
||||
)
|
||||
from utils.database import acknowledge_dsc_alert, get_dsc_alert, store_dsc_alert
|
||||
|
||||
alert_id = store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
|
||||
@@ -204,11 +197,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_acknowledge_dsc_alert_with_notes(self, temp_db):
|
||||
"""Test acknowledging with notes."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alert,
|
||||
acknowledge_dsc_alert
|
||||
)
|
||||
from utils.database import acknowledge_dsc_alert, get_dsc_alert, store_dsc_alert
|
||||
|
||||
alert_id = store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
|
||||
@@ -227,11 +216,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_get_dsc_alert_summary(self, temp_db):
|
||||
"""Test getting alert summary counts."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alert_summary,
|
||||
acknowledge_dsc_alert
|
||||
)
|
||||
from utils.database import acknowledge_dsc_alert, get_dsc_alert_summary, store_dsc_alert
|
||||
|
||||
# Create various alerts
|
||||
store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
@@ -264,12 +249,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_cleanup_old_dsc_alerts(self, temp_db):
|
||||
"""Test cleanup function behavior."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alerts,
|
||||
acknowledge_dsc_alert,
|
||||
cleanup_old_dsc_alerts
|
||||
)
|
||||
from utils.database import acknowledge_dsc_alert, cleanup_old_dsc_alerts, get_dsc_alerts, store_dsc_alert
|
||||
|
||||
# Create and acknowledge some alerts
|
||||
id1 = store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
@@ -294,11 +274,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_cleanup_preserves_unacknowledged(self, temp_db):
|
||||
"""Test cleanup preserves unacknowledged alerts regardless of age."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alerts,
|
||||
cleanup_old_dsc_alerts
|
||||
)
|
||||
from utils.database import cleanup_old_dsc_alerts, get_dsc_alerts, store_dsc_alert
|
||||
|
||||
# Create unacknowledged alerts
|
||||
store_dsc_alert('232123456', '100', 'DISTRESS')
|
||||
@@ -314,7 +290,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_store_alert_with_raw_message(self, temp_db):
|
||||
"""Test storing alert with raw message data."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alert
|
||||
from utils.database import get_dsc_alert, store_dsc_alert
|
||||
|
||||
raw = '100023212345603660000110010010000000000127'
|
||||
|
||||
@@ -330,7 +306,7 @@ class TestDSCAlertsCRUD:
|
||||
|
||||
def test_store_alert_with_destination(self, temp_db):
|
||||
"""Test storing alert with destination MMSI."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alert
|
||||
from utils.database import get_dsc_alert, store_dsc_alert
|
||||
|
||||
alert_id = store_dsc_alert(
|
||||
source_mmsi='232123456',
|
||||
@@ -349,11 +325,11 @@ class TestDSCDatabaseIntegration:
|
||||
def test_full_alert_lifecycle(self, temp_db):
|
||||
"""Test complete lifecycle of a DSC alert."""
|
||||
from utils.database import (
|
||||
store_dsc_alert,
|
||||
get_dsc_alert,
|
||||
get_dsc_alerts,
|
||||
acknowledge_dsc_alert,
|
||||
get_dsc_alert_summary
|
||||
get_dsc_alert,
|
||||
get_dsc_alert_summary,
|
||||
get_dsc_alerts,
|
||||
store_dsc_alert,
|
||||
)
|
||||
|
||||
# 1. Store a distress alert
|
||||
@@ -396,7 +372,7 @@ class TestDSCDatabaseIntegration:
|
||||
|
||||
def test_multiple_vessel_alerts(self, temp_db):
|
||||
"""Test handling alerts from multiple vessels."""
|
||||
from utils.database import store_dsc_alert, get_dsc_alerts
|
||||
from utils.database import get_dsc_alerts, store_dsc_alert
|
||||
|
||||
# Simulate multiple vessels in distress
|
||||
vessels = [
|
||||
@@ -405,7 +381,7 @@ class TestDSCDatabaseIntegration:
|
||||
('351234567', 'Panama', 'COLLISION'),
|
||||
]
|
||||
|
||||
for mmsi, country, nature in vessels:
|
||||
for mmsi, _country, nature in vessels:
|
||||
store_dsc_alert(
|
||||
source_mmsi=mmsi,
|
||||
format_code='100',
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
"""Tests for the KiwiSDR WebSocket audio client."""
|
||||
|
||||
import struct
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from utils.kiwisdr import (
|
||||
KiwiSDRClient,
|
||||
KIWI_DEFAULT_PORT,
|
||||
KIWI_SAMPLE_RATE,
|
||||
KIWI_SND_HEADER_SIZE,
|
||||
KIWI_DEFAULT_PORT,
|
||||
MODE_FILTERS,
|
||||
VALID_MODES,
|
||||
KiwiSDRClient,
|
||||
parse_host_port,
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# parse_host_port tests
|
||||
# ============================================
|
||||
|
||||
@@ -9,10 +9,10 @@ Tests cover:
|
||||
"""
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
# =============================================================================
|
||||
# Utility Module Tests
|
||||
@@ -173,9 +173,10 @@ class TestPSKParsing:
|
||||
|
||||
def test_parse_psk_base64(self):
|
||||
"""Should decode base64 PSK."""
|
||||
from utils.meshtastic import MeshtasticClient
|
||||
import base64
|
||||
|
||||
from utils.meshtastic import MeshtasticClient
|
||||
|
||||
client = MeshtasticClient()
|
||||
# 32-byte key encoded as base64
|
||||
key = b'A' * 32
|
||||
@@ -197,9 +198,10 @@ class TestPSKParsing:
|
||||
|
||||
def test_parse_psk_simple_passphrase(self):
|
||||
"""Should hash simple passphrase to 32-byte key."""
|
||||
from utils.meshtastic import MeshtasticClient
|
||||
import hashlib
|
||||
|
||||
from utils.meshtastic import MeshtasticClient
|
||||
|
||||
client = MeshtasticClient()
|
||||
result = client._parse_psk('simple:MySecretPassword')
|
||||
|
||||
@@ -218,9 +220,10 @@ class TestPSKParsing:
|
||||
|
||||
def test_parse_psk_raw_base64(self):
|
||||
"""Should accept raw base64 without prefix."""
|
||||
from utils.meshtastic import MeshtasticClient
|
||||
import base64
|
||||
|
||||
from utils.meshtastic import MeshtasticClient
|
||||
|
||||
client = MeshtasticClient()
|
||||
key = b'B' * 16
|
||||
encoded = base64.b64encode(key).decode()
|
||||
@@ -261,6 +264,7 @@ class TestMeshtasticRoutes:
|
||||
def app(self):
|
||||
"""Create Flask test app."""
|
||||
from flask import Flask
|
||||
|
||||
from routes.meshtastic import meshtastic_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
+16
-14
@@ -1,8 +1,10 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import importlib.metadata
|
||||
import tomllib
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import tomllib
|
||||
|
||||
|
||||
def get_root_path():
|
||||
return Path(__file__).parent.parent
|
||||
@@ -16,7 +18,7 @@ def parse_txt_requirements(file_path):
|
||||
if not file_path.exists():
|
||||
return set()
|
||||
packages = set()
|
||||
with open(file_path, "r") as f:
|
||||
with open(file_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith(("#", "-e", "git+", "-r")):
|
||||
@@ -28,7 +30,7 @@ def parse_toml_section(data, section_type="main"):
|
||||
"""Extracts full requirement strings from pyproject.toml including optional sections."""
|
||||
packages = set()
|
||||
project = data.get("project", {})
|
||||
|
||||
|
||||
if section_type == "main":
|
||||
deps = project.get("dependencies", [])
|
||||
elif section_type == "optional":
|
||||
@@ -37,7 +39,7 @@ def parse_toml_section(data, section_type="main"):
|
||||
deps = project.get("optional-dependencies", {}).get("dev", [])
|
||||
if not deps:
|
||||
deps = data.get("dependency-groups", {}).get("dev", [])
|
||||
|
||||
|
||||
for req in deps:
|
||||
packages.add(_clean_string(req))
|
||||
return packages
|
||||
@@ -54,7 +56,7 @@ def test_dependency_files_integrity():
|
||||
# Validate Production Sync (Main + Optionals)
|
||||
txt_main = parse_txt_requirements(root / "requirements.txt")
|
||||
toml_main = parse_toml_section(toml_data, "main") | parse_toml_section(toml_data, "optional")
|
||||
|
||||
|
||||
assert txt_main == toml_main, (
|
||||
f"Production version mismatch!\n"
|
||||
f"Only in TXT: {txt_main - toml_main}\n"
|
||||
@@ -75,10 +77,10 @@ def test_environment_vs_toml():
|
||||
root = get_root_path()
|
||||
with open(root / "pyproject.toml", "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
|
||||
|
||||
all_declared = (
|
||||
parse_toml_section(data, "main") |
|
||||
parse_toml_section(data, "optional") |
|
||||
parse_toml_section(data, "main") |
|
||||
parse_toml_section(data, "optional") |
|
||||
parse_toml_section(data, "dev")
|
||||
)
|
||||
_verify_installation(all_declared, "TOML")
|
||||
@@ -87,7 +89,7 @@ def test_environment_vs_requirements():
|
||||
"""3. Verifies that installed packages satisfy .txt requirements."""
|
||||
root = get_root_path()
|
||||
all_txt_deps = (
|
||||
parse_txt_requirements(root / "requirements.txt") |
|
||||
parse_txt_requirements(root / "requirements.txt") |
|
||||
parse_txt_requirements(root / "requirements-dev.txt")
|
||||
)
|
||||
_verify_installation(all_txt_deps, "requirements.txt")
|
||||
@@ -95,15 +97,15 @@ def test_environment_vs_requirements():
|
||||
def _verify_installation(package_set, source_name):
|
||||
"""Helper to check if declared versions match installed versions."""
|
||||
missing_or_wrong = []
|
||||
|
||||
|
||||
for req in package_set:
|
||||
# Split name from version
|
||||
parts = re.split(r'==|>=|~=|<=|>|<', req)
|
||||
raw_name = parts[0].strip()
|
||||
|
||||
|
||||
# CLEAN EXTRAS: "qrcode[pil]" -> "qrcode"
|
||||
clean_name = re.sub(r'\[.*\]', '', raw_name)
|
||||
|
||||
|
||||
try:
|
||||
installed_ver = importlib.metadata.version(clean_name)
|
||||
if "==" in req:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Tests for Flask routes and API endpoints."""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from routes.listening_post import _rtl_fm_demod_mode as listening_post_rtl_mode
|
||||
from utils.sdr.base import SDRDevice, SDRType
|
||||
from utils.sdr.rtlsdr import RTLSDRCommandBuilder, _rtl_fm_demod_mode as builder_rtl_mode
|
||||
from utils.sdr.rtlsdr import RTLSDRCommandBuilder
|
||||
from utils.sdr.rtlsdr import _rtl_fm_demod_mode as builder_rtl_mode
|
||||
|
||||
|
||||
def _dummy_rtlsdr_device() -> SDRDevice:
|
||||
|
||||
+10
-9
@@ -1,7 +1,8 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from unittest.mock import patch, MagicMock
|
||||
from flask import Flask
|
||||
|
||||
from routes.satellite import satellite_bp
|
||||
|
||||
|
||||
@@ -38,11 +39,11 @@ def test_fetch_celestrak_invalid_category(client):
|
||||
def test_update_tle_success(mock_urlopen, client):
|
||||
"""Simulate a successful response from CelesTrak."""
|
||||
mock_content = (
|
||||
"ISS (ZARYA)\n"
|
||||
"1 25544U 98067A 23321.52083333 .00016717 00000-0 30171-3 0 9992\n"
|
||||
"2 25544 51.6416 20.4567 0004561 45.3212 67.8912 15.49876543123456\n"
|
||||
).encode('utf-8')
|
||||
|
||||
b"ISS (ZARYA)\n"
|
||||
b"1 25544U 98067A 23321.52083333 .00016717 00000-0 30171-3 0 9992\n"
|
||||
b"2 25544 51.6416 20.4567 0004561 45.3212 67.8912 15.49876543123456\n"
|
||||
)
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = mock_content
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
@@ -58,7 +59,7 @@ def test_get_satellite_position_skyfield_error(mock_load, client):
|
||||
"""Test behavior when Skyfield fails or data is missing."""
|
||||
# Force the timescale load to fail
|
||||
mock_load.side_effect = Exception("Skyfield error")
|
||||
|
||||
|
||||
payload = {
|
||||
"latitude": 51.5,
|
||||
"longitude": -0.1,
|
||||
@@ -79,4 +80,4 @@ def test_predict_passes_empty_cache(client):
|
||||
}
|
||||
response = client.post('/satellite/predict', json=payload)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json['passes']) == 0
|
||||
assert len(response.json['passes']) == 0
|
||||
|
||||
@@ -13,12 +13,9 @@ Tests cover:
|
||||
- Confidence level calculations
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from utils.signal_guess import (
|
||||
SignalGuessingEngine,
|
||||
SignalGuessResult,
|
||||
SignalAlternative,
|
||||
Confidence,
|
||||
SignalGuessingEngine,
|
||||
guess_signal_type,
|
||||
guess_signal_type_dict,
|
||||
)
|
||||
|
||||
@@ -4,14 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.subghz import SubGhzManager, SubGhzCapture
|
||||
from utils.subghz import SubGhzCapture, SubGhzManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -32,7 +29,7 @@ def manager(tmp_data_dir):
|
||||
class TestSubGhzManagerInit:
|
||||
def test_creates_data_dirs(self, tmp_path):
|
||||
data_dir = tmp_path / 'new_subghz'
|
||||
mgr = SubGhzManager(data_dir=data_dir)
|
||||
SubGhzManager(data_dir=data_dir)
|
||||
assert (data_dir / 'captures').is_dir()
|
||||
|
||||
def test_active_mode_idle(self, manager):
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@ the signature engine correctly identifies them with appropriate confidence.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.bluetooth.tracker_signatures import (
|
||||
APPLE_COMPANY_ID,
|
||||
TrackerConfidence,
|
||||
TrackerSignatureEngine,
|
||||
TrackerType,
|
||||
TrackerConfidence,
|
||||
detect_tracker,
|
||||
get_tracker_engine,
|
||||
APPLE_COMPANY_ID,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SAMPLE PAYLOADS FROM REAL DEVICES
|
||||
# =============================================================================
|
||||
|
||||
+2
-3
@@ -1,9 +1,8 @@
|
||||
"""Tests for utility modules."""
|
||||
|
||||
import pytest
|
||||
from utils.process import is_valid_mac, is_valid_channel
|
||||
from utils.dependencies import check_tool
|
||||
from data.oui import get_manufacturer
|
||||
from utils.dependencies import check_tool
|
||||
from utils.process import is_valid_channel, is_valid_mac
|
||||
|
||||
|
||||
class TestMacValidation:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""Comprehensive tests for validation utilities."""
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.validation import (
|
||||
validate_device_index,
|
||||
validate_frequency,
|
||||
validate_gain,
|
||||
validate_device_index,
|
||||
validate_rtl_tcp_host,
|
||||
validate_rtl_tcp_port,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for the Waterfall / Spectrogram endpoints."""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -6,20 +6,16 @@ and image handling.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, call, mock_open
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from utils.weather_sat import (
|
||||
WEATHER_SATELLITES,
|
||||
CaptureProgress,
|
||||
WeatherSatDecoder,
|
||||
WeatherSatImage,
|
||||
CaptureProgress,
|
||||
WEATHER_SATELLITES,
|
||||
get_weather_sat_decoder,
|
||||
is_weather_sat_available,
|
||||
)
|
||||
|
||||
@@ -6,8 +6,9 @@ and ground track generation.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.weather_sat_predict import _format_utc_iso, predict_passes
|
||||
@@ -526,7 +527,7 @@ class TestPredictPasses:
|
||||
patch('utils.weather_sat_predict.EarthSatellite'), \
|
||||
patch('utils.weather_sat_predict.find_discrete', return_value=([], [])):
|
||||
|
||||
passes = predict_passes(lat=51.5, lon=-0.1, hours=24, min_elevation=15)
|
||||
predict_passes(lat=51.5, lon=-0.1, hours=24, min_elevation=15)
|
||||
# Should not raise
|
||||
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
|
||||
@@ -7,12 +7,11 @@ Covers all weather_sat endpoints: /status, /satellites, /start, /test-decode,
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, mock_open
|
||||
import pytest
|
||||
|
||||
from utils.weather_sat import WeatherSatImage, WEATHER_SATELLITES
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from utils.weather_sat import WeatherSatImage
|
||||
|
||||
|
||||
class TestWeatherSatRoutes:
|
||||
@@ -69,7 +68,7 @@ class TestWeatherSatRoutes:
|
||||
"""POST /weather-sat/start successfully starts capture."""
|
||||
with patch('routes.weather_sat.is_weather_sat_available', return_value=True), \
|
||||
patch('routes.weather_sat.get_weather_sat_decoder') as mock_get, \
|
||||
patch('routes.weather_sat.queue.Queue') as mock_queue:
|
||||
patch('routes.weather_sat.queue.Queue'):
|
||||
|
||||
mock_decoder = MagicMock()
|
||||
mock_decoder.is_running = False
|
||||
@@ -207,7 +206,7 @@ class TestWeatherSatRoutes:
|
||||
"""POST /weather-sat/start when SDR device is busy."""
|
||||
with patch('routes.weather_sat.is_weather_sat_available', return_value=True), \
|
||||
patch('routes.weather_sat.get_weather_sat_decoder') as mock_get, \
|
||||
patch('app.claim_sdr_device', return_value='Device busy with pager') as mock_claim:
|
||||
patch('app.claim_sdr_device', return_value='Device busy with pager'):
|
||||
|
||||
mock_decoder = MagicMock()
|
||||
mock_decoder.is_running = False
|
||||
@@ -548,7 +547,7 @@ class TestWeatherSatRoutes:
|
||||
mock_get.return_value = mock_decoder
|
||||
mock_send.return_value = MagicMock()
|
||||
|
||||
response = client.get('/weather-sat/images/test_image.png')
|
||||
client.get('/weather-sat/images/test_image.png')
|
||||
mock_send.assert_called_once()
|
||||
call_args = mock_send.call_args
|
||||
assert call_args[1]['mimetype'] == 'image/png'
|
||||
|
||||
@@ -7,16 +7,16 @@ and automatic capture execution.
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from utils.weather_sat_scheduler import (
|
||||
WeatherSatScheduler,
|
||||
ScheduledPass,
|
||||
get_weather_sat_scheduler,
|
||||
WeatherSatScheduler,
|
||||
_parse_utc_iso,
|
||||
get_weather_sat_scheduler,
|
||||
)
|
||||
|
||||
|
||||
@@ -742,8 +742,8 @@ class TestSchedulerConfiguration:
|
||||
def test_config_constants(self):
|
||||
"""Scheduler should have configuration constants."""
|
||||
from utils.weather_sat_scheduler import (
|
||||
WEATHER_SAT_SCHEDULE_REFRESH_MINUTES,
|
||||
WEATHER_SAT_CAPTURE_BUFFER_SECONDS,
|
||||
WEATHER_SAT_SCHEDULE_REFRESH_MINUTES,
|
||||
)
|
||||
|
||||
assert isinstance(WEATHER_SAT_SCHEDULE_REFRESH_MINUTES, int)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""Tests for the HF/Shortwave WebSDR integration."""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
import pytest
|
||||
from routes.websdr import _parse_gps_coord, _haversine
|
||||
from utils.kiwisdr import parse_host_port
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from routes.websdr import _haversine, _parse_gps_coord
|
||||
from utils.kiwisdr import parse_host_port
|
||||
|
||||
# ============================================
|
||||
# Helper function tests
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ class TestWeFaxStations:
|
||||
station_callsign='NOJ',
|
||||
frequency_reference='invalid',
|
||||
)
|
||||
assert False, "Expected ValueError for invalid frequency_reference"
|
||||
raise AssertionError("Expected ValueError for invalid frequency_reference")
|
||||
except ValueError as exc:
|
||||
assert 'frequency_reference' in str(exc)
|
||||
|
||||
|
||||
+38
-35
@@ -1,10 +1,13 @@
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch, mock_open
|
||||
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 wifi_bp, parse_airodump_csv
|
||||
from routes.wifi import parse_airodump_csv, wifi_bp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_app_module(mocker):
|
||||
@@ -37,11 +40,11 @@ def test_parse_airodump_csv(mocker):
|
||||
"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
|
||||
@@ -53,10 +56,10 @@ 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
|
||||
@@ -67,18 +70,18 @@ def test_toggle_monitor_start_success(client, mocker):
|
||||
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()
|
||||
|
||||
mock_app_module.wifi_process = MagicMock()
|
||||
|
||||
response = client.post('/wifi/scan/start', json={'interface': 'wlan0mon'})
|
||||
data = response.get_json()
|
||||
assert data['status'] == 'error'
|
||||
@@ -86,21 +89,21 @@ def test_start_scan_already_running(client, mock_app_module):
|
||||
|
||||
def test_start_scan_execution(client, mock_app_module, mocker):
|
||||
"""Test the full command construction of airodump-ng."""
|
||||
mock_app_module.wifi_process = None
|
||||
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_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
|
||||
@@ -110,9 +113,9 @@ 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()
|
||||
@@ -123,14 +126,14 @@ def test_send_deauth_success(client, mock_app_module, mocker):
|
||||
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]
|
||||
@@ -145,10 +148,10 @@ def test_capture_handshake_start(client, mock_app_module, mocker):
|
||||
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
|
||||
@@ -158,13 +161,13 @@ def test_check_handshake_status_found(client, mocker):
|
||||
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 --- ###
|
||||
@@ -184,22 +187,22 @@ 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="",
|
||||
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'
|
||||
@@ -212,10 +215,10 @@ def test_get_wifi_networks(client, mock_app_module):
|
||||
'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']
|
||||
assert 'AA:BB:CC:DD:EE:FF' in data['handshakes']
|
||||
|
||||
Reference in New Issue
Block a user