Files
intercept/tests/test_gsm_spy.py
2026-02-08 07:04:10 -06:00

303 lines
11 KiB
Python

"""Unit tests for GSM Spy parsing and validation functions."""
import pytest
from routes.gsm_spy import (
parse_grgsm_scanner_output,
parse_tshark_output,
arfcn_to_frequency,
validate_band_names,
REGIONAL_BANDS
)
class TestParseGrgsmScannerOutput:
"""Tests for parse_grgsm_scanner_output()."""
def test_valid_table_row(self):
"""Test parsing a valid scanner output table row."""
line = " 23 | 940.6 | 31245 | 1234 | 214 | 01 | -48"
result = parse_grgsm_scanner_output(line)
assert result is not None
assert result['type'] == 'tower'
assert result['arfcn'] == 23
assert result['frequency'] == 940.6
assert result['cid'] == 31245
assert result['lac'] == 1234
assert result['mcc'] == 214
assert result['mnc'] == 1
assert result['signal_strength'] == -48.0
assert 'timestamp' in result
def test_header_line(self):
"""Test that header lines are skipped."""
line = "ARFCN | Freq (MHz) | CID | LAC | MCC | MNC | Power (dB)"
result = parse_grgsm_scanner_output(line)
assert result is None
def test_separator_line(self):
"""Test that separator lines are skipped."""
line = "--------------------------------------------------------------------"
result = parse_grgsm_scanner_output(line)
assert result is None
def test_progress_line(self):
"""Test that progress lines are skipped."""
line = "Scanning: 50% complete"
result = parse_grgsm_scanner_output(line)
assert result is None
def test_found_line(self):
"""Test that 'Found X towers' lines are skipped."""
line = "Found 5 towers"
result = parse_grgsm_scanner_output(line)
assert result is None
def test_invalid_data(self):
"""Test handling of invalid data."""
line = " abc | xyz | invalid | data | bad | bad | bad"
result = parse_grgsm_scanner_output(line)
assert result is None
def test_empty_line(self):
"""Test handling of empty lines."""
result = parse_grgsm_scanner_output("")
assert result is None
def test_partial_data(self):
"""Test handling of incomplete table rows."""
line = " 23 | 940.6 | 31245" # Missing fields
result = parse_grgsm_scanner_output(line)
assert result is None
class TestParseTsharkOutput:
"""Tests for parse_tshark_output()."""
def test_valid_full_output(self):
"""Test parsing tshark output with all fields."""
line = "5\t0xABCD1234\t123456789012345\t1234\t31245"
result = parse_tshark_output(line)
assert result is not None
assert result['type'] == 'device'
assert result['ta_value'] == 5
assert result['tmsi'] == '0xABCD1234'
assert result['imsi'] == '123456789012345'
assert result['lac'] == 1234
assert result['cid'] == 31245
assert result['distance_meters'] == 5 * 554 # TA * 554 meters
assert 'timestamp' in result
def test_missing_optional_fields(self):
"""Test parsing with missing optional fields (empty tabs)."""
line = "3\t\t\t1234\t31245"
result = parse_tshark_output(line)
assert result is not None
assert result['ta_value'] == 3
assert result['tmsi'] is None
assert result['imsi'] is None
assert result['lac'] == 1234
assert result['cid'] == 31245
def test_no_ta_value(self):
"""Test parsing without TA value (empty field)."""
# When TA is empty, int('') will fail, so the parse returns None
# This is the current behavior - the function expects valid integers or valid empty handling
line = "\t0xABCD1234\t123456789012345\t1234\t31245"
result = parse_tshark_output(line)
# Current implementation will fail to parse this due to int('') failing
assert result is None
def test_invalid_line(self):
"""Test handling of invalid tshark output."""
line = "invalid data"
result = parse_tshark_output(line)
assert result is None
def test_empty_line(self):
"""Test handling of empty lines."""
result = parse_tshark_output("")
assert result is None
def test_partial_fields(self):
"""Test with fewer than 5 fields."""
line = "5\t0xABCD1234" # Only 2 fields
result = parse_tshark_output(line)
assert result is None
class TestArfcnToFrequency:
"""Tests for arfcn_to_frequency()."""
def test_gsm850_arfcn(self):
"""Test ARFCN in GSM850 band."""
# GSM850: ARFCN 128-251, 869-894 MHz
arfcn = 128
freq = arfcn_to_frequency(arfcn)
assert freq == 869000000 # 869 MHz
arfcn = 251
freq = arfcn_to_frequency(arfcn)
assert freq == 893600000 # 893.6 MHz
def test_egsm900_arfcn(self):
"""Test ARFCN in EGSM900 band."""
# EGSM900: ARFCN 0-124, 925-960 MHz
arfcn = 0
freq = arfcn_to_frequency(arfcn)
assert freq == 925000000 # 925 MHz
arfcn = 124
freq = arfcn_to_frequency(arfcn)
assert freq == 949800000 # 949.8 MHz
def test_dcs1800_arfcn(self):
"""Test ARFCN in DCS1800 band."""
# DCS1800: ARFCN 512-885, 1805-1880 MHz
# Note: ARFCN 512 also exists in PCS1900 and will match that first
# Use ARFCN 811+ which is only in DCS1800
arfcn = 811 # Beyond PCS1900 range (512-810)
freq = arfcn_to_frequency(arfcn)
# 811 is ARFCN offset from 512, so freq = 1805MHz + (811-512)*200kHz
expected = 1805000000 + (811 - 512) * 200000
assert freq == expected
arfcn = 885
freq = arfcn_to_frequency(arfcn)
assert freq == 1879600000 # 1879.6 MHz
def test_pcs1900_arfcn(self):
"""Test ARFCN in PCS1900 band."""
# PCS1900: ARFCN 512-810, 1930-1990 MHz
# Note: overlaps with DCS1800 ARFCN range, but different frequencies
arfcn = 512
freq = arfcn_to_frequency(arfcn)
# Will match first band (DCS1800 in Europe config)
assert freq > 0
def test_invalid_arfcn(self):
"""Test ARFCN outside known ranges."""
with pytest.raises(ValueError, match="not found in any known GSM band"):
arfcn_to_frequency(9999)
with pytest.raises(ValueError):
arfcn_to_frequency(-1)
def test_arfcn_200khz_spacing(self):
"""Test that ARFCNs are 200kHz apart."""
arfcn1 = 128
arfcn2 = 129
freq1 = arfcn_to_frequency(arfcn1)
freq2 = arfcn_to_frequency(arfcn2)
assert freq2 - freq1 == 200000 # 200 kHz
class TestValidateBandNames:
"""Tests for validate_band_names()."""
def test_valid_americas_bands(self):
"""Test valid band names for Americas region."""
bands = ['GSM850', 'PCS1900']
result, error = validate_band_names(bands, 'Americas')
assert result == bands
assert error is None
def test_valid_europe_bands(self):
"""Test valid band names for Europe region."""
# Note: Europe uses EGSM900, not GSM900
bands = ['EGSM900', 'DCS1800', 'GSM850', 'GSM800']
result, error = validate_band_names(bands, 'Europe')
assert result == bands
assert error is None
def test_valid_asia_bands(self):
"""Test valid band names for Asia region."""
# Note: Asia uses EGSM900, not GSM900
bands = ['EGSM900', 'DCS1800']
result, error = validate_band_names(bands, 'Asia')
assert result == bands
assert error is None
def test_invalid_band_for_region(self):
"""Test invalid band name for a region."""
bands = ['GSM900', 'INVALID_BAND']
result, error = validate_band_names(bands, 'Americas')
assert result == []
assert error is not None
assert 'Invalid bands' in error
assert 'INVALID_BAND' in error
def test_invalid_region(self):
"""Test invalid region name."""
bands = ['GSM900']
result, error = validate_band_names(bands, 'InvalidRegion')
assert result == []
assert error is not None
assert 'Invalid region' in error
def test_empty_bands_list(self):
"""Test with empty bands list."""
result, error = validate_band_names([], 'Americas')
assert result == []
assert error is None
def test_single_valid_band(self):
"""Test with single valid band."""
bands = ['GSM850']
result, error = validate_band_names(bands, 'Americas')
assert result == ['GSM850']
assert error is None
def test_case_sensitive_band_names(self):
"""Test that band names are case-sensitive."""
bands = ['gsm850'] # lowercase
result, error = validate_band_names(bands, 'Americas')
assert result == []
assert error is not None
def test_multiple_invalid_bands(self):
"""Test with multiple invalid bands."""
bands = ['INVALID1', 'GSM850', 'INVALID2']
result, error = validate_band_names(bands, 'Americas')
assert result == []
assert error is not None
assert 'INVALID1' in error
assert 'INVALID2' in error
class TestRegionalBandsConfig:
"""Tests for REGIONAL_BANDS configuration."""
def test_all_regions_defined(self):
"""Test that all expected regions are defined."""
assert 'Americas' in REGIONAL_BANDS
assert 'Europe' in REGIONAL_BANDS
assert 'Asia' in REGIONAL_BANDS
def test_all_bands_have_required_fields(self):
"""Test that all bands have required configuration fields."""
for region, bands in REGIONAL_BANDS.items():
for band_name, band_config in bands.items():
assert 'start' in band_config
assert 'end' in band_config
assert 'arfcn_start' in band_config
assert 'arfcn_end' in band_config
def test_frequency_ranges_valid(self):
"""Test that frequency ranges are positive and start < end."""
for region, bands in REGIONAL_BANDS.items():
for band_name, band_config in bands.items():
assert band_config['start'] > 0
assert band_config['end'] > 0
assert band_config['start'] < band_config['end']
def test_arfcn_ranges_valid(self):
"""Test that ARFCN ranges are valid."""
for region, bands in REGIONAL_BANDS.items():
for band_name, band_config in bands.items():
assert band_config['arfcn_start'] >= 0
assert band_config['arfcn_end'] >= 0
assert band_config['arfcn_start'] <= band_config['arfcn_end']