Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ coverage.xml
.hypothesis/
.pytest_cache/

# Claude configuration
.claude/*

# Translations
*.mo
*.pot
Expand Down
1,251 changes: 1,251 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
[tool.poetry]
name = "crocodilehunter"
version = "0.1.0"
description = "A tool for detecting fake cell towers"
authors = ["EFF <noreply@eff.org>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.8"
access-points = "0.4.66"
alembic = "^1.5.7"
certifi = "^2020.12.5"
chardet = "^3.0.4"
click = "^8.0.0"
coloredlogs = "10.0"
flask-migrate = "^2.5.3"
flask-script = "^2.0.6"
flask-sqlalchemy = "*"
flask = "^2.0.0"
geographiclib = "^1.50"
geopy = "1.20.0"
gpsd-py3 = "0.3.0"
humanfriendly = "^9.1"
idna = "^2.8"
itsdangerous = "^2.0.0"
jinja2 = "^3.0.0"
mako = "^1.2.2"
markupsafe = "^2.1.0"
mysqlclient = "*"
numpy = "^1.24.0"
python-dateutil = "^2.8.1"
python-editor = "^1.0.4"
requests = "^2.28.0"
scipy = "*"
six = "^1.15.0"
sqlalchemy-utils = "*"
sqlalchemy = "*"
urllib3 = "*"
verboselogs = "1.7"
werkzeug = "^2.0.0"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"

[tool.pytest.ini_options]
minversion = "6.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-ra",
"--strict-markers",
"--cov=src",
"--cov-report=html",
"--cov-report=xml",
"--cov-report=term-missing:skip-covered",
"--cov-fail-under=0", # TODO: Change to 80 once tests are written
"-vv"
]
markers = [
"unit: marks tests as unit tests (fast, isolated)",
"integration: marks tests as integration tests (may require external resources)",
"slow: marks tests as slow (deselect with '-m \"not slow\"')"
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning"
]

[tool.coverage.run]
source = ["src"]
omit = [
"*/tests/*",
"*/test_*",
"*/__init__.py",
"*/migrations/*",
"*/config.py",
"*/setup.py"
]

[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
fail_under = 0 # TODO: Change to 80 once tests are written
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod"
]

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
138 changes: 138 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import os
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock
import pytest


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path, ignore_errors=True)


@pytest.fixture
def temp_file(temp_dir):
"""Create a temporary file within the temp directory."""
def _create_temp_file(filename="test_file.txt", content=""):
file_path = temp_dir / filename
file_path.write_text(content)
return file_path
return _create_temp_file


@pytest.fixture
def mock_config():
"""Provide a mock configuration object."""
config = MagicMock()
config.get.return_value = "mock_value"
config.items.return_value = [("key1", "value1"), ("key2", "value2")]
return config


@pytest.fixture
def mock_database():
"""Provide a mock database connection."""
db = Mock()
db.execute.return_value = Mock(fetchall=lambda: [])
db.commit.return_value = None
db.rollback.return_value = None
return db


@pytest.fixture
def mock_flask_app():
"""Create a mock Flask application for testing."""
app = Mock()
app.config = {
'TESTING': True,
'DEBUG': False,
'SECRET_KEY': 'test-secret-key',
'DATABASE_URI': 'sqlite:///:memory:'
}
return app


@pytest.fixture
def mock_request():
"""Create a mock Flask request object."""
request = Mock()
request.method = 'GET'
request.args = {}
request.form = {}
request.json = {}
request.headers = {}
return request


@pytest.fixture
def sample_data():
"""Provide sample data for testing."""
return {
'towers': [
{'id': 1, 'mcc': 310, 'mnc': 410, 'lac': 1234, 'cid': 5678},
{'id': 2, 'mcc': 310, 'mnc': 260, 'lac': 2345, 'cid': 6789}
],
'measurements': [
{'id': 1, 'tower_id': 1, 'signal_strength': -85, 'timestamp': '2024-01-01T00:00:00'},
{'id': 2, 'tower_id': 2, 'signal_strength': -90, 'timestamp': '2024-01-01T00:01:00'}
]
}


@pytest.fixture
def mock_api_response():
"""Create a mock API response."""
def _create_response(status_code=200, json_data=None, text=""):
response = Mock()
response.status_code = status_code
response.json.return_value = json_data or {}
response.text = text
response.ok = status_code < 400
return response
return _create_response


@pytest.fixture(autouse=True)
def reset_environment():
"""Reset environment variables before each test."""
original_env = os.environ.copy()
yield
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def mock_gps_data():
"""Provide mock GPS data for testing."""
return {
'latitude': 37.7749,
'longitude': -122.4194,
'altitude': 10.0,
'speed': 0.0,
'timestamp': '2024-01-01T00:00:00Z'
}


@pytest.fixture
def mock_logger():
"""Create a mock logger for testing."""
logger = Mock()
logger.debug = Mock()
logger.info = Mock()
logger.warning = Mock()
logger.error = Mock()
logger.critical = Mock()
return logger


@pytest.fixture
def isolated_filesystem(tmp_path):
"""Create an isolated filesystem for testing file operations."""
original_cwd = os.getcwd()
os.chdir(tmp_path)
yield tmp_path
os.chdir(original_cwd)
Empty file added tests/integration/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import pytest
import sys
from pathlib import Path


class TestSetupValidation:
"""Validation tests to ensure the testing infrastructure is properly configured."""

def test_pytest_installed(self):
"""Verify pytest is available."""
assert 'pytest' in sys.modules or 'pytest' in str(sys.path)

def test_project_structure_exists(self):
"""Verify the expected project structure is in place."""
project_root = Path(__file__).parent.parent

assert project_root.exists()
assert (project_root / 'tests').exists()
assert (project_root / 'tests' / '__init__.py').exists()
assert (project_root / 'tests' / 'unit').exists()
assert (project_root / 'tests' / 'unit' / '__init__.py').exists()
assert (project_root / 'tests' / 'integration').exists()
assert (project_root / 'tests' / 'integration' / '__init__.py').exists()
assert (project_root / 'tests' / 'conftest.py').exists()

def test_pyproject_toml_exists(self):
"""Verify pyproject.toml exists and contains testing configuration."""
project_root = Path(__file__).parent.parent
pyproject_path = project_root / 'pyproject.toml'

assert pyproject_path.exists()

content = pyproject_path.read_text()
assert '[tool.pytest.ini_options]' in content
assert '[tool.coverage.run]' in content
assert 'pytest' in content
assert 'pytest-cov' in content
assert 'pytest-mock' in content

@pytest.mark.unit
def test_unit_marker_works(self):
"""Verify the unit test marker is properly configured."""
assert True

@pytest.mark.integration
def test_integration_marker_works(self):
"""Verify the integration test marker is properly configured."""
assert True

@pytest.mark.slow
def test_slow_marker_works(self):
"""Verify the slow test marker is properly configured."""
assert True

def test_fixtures_available(self, temp_dir, mock_config, sample_data):
"""Verify custom fixtures from conftest.py are available."""
assert temp_dir.exists()
assert mock_config is not None
assert 'towers' in sample_data
assert 'measurements' in sample_data

def test_coverage_configured(self):
"""Verify coverage is properly configured."""
try:
import coverage
assert True
except ImportError:
pytest.fail("Coverage module not available")

def test_mock_available(self):
"""Verify pytest-mock is available."""
try:
from pytest_mock import MockerFixture
assert True
except ImportError:
pytest.fail("pytest-mock not available")
Empty file added tests/unit/__init__.py
Empty file.