diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c73e0bd..140b89c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,20 +1,20 @@ -# Руководство для контрибьюторов netsleuth +# Руководство для контрибьюторов Netsleuth -Спасибо за интерес к проекту netsleuth! Это руководство поможет вам начать вносить свой вклад. +Спасибо за интерес к проекту! Это руководство поможет вам начать вносить свой вклад. ## 🚀 Быстрый старт -### 1. Установка зависимостей +### 1. Настройка окружения ```bash # Клонируйте репозиторий -git clone https://github.com/netsleuth/netsleuth.git +git clone https://github.com/egorovandrey/netsleuth.git cd netsleuth -# Установите uv (менеджер пакетов) +# Установите uv (если ещё не установлен) curl -LsSf https://astral.sh/uv/install.sh | sh -# Синхронизируйте зависимости +# Создайте виртуальное окружение и установите зависимости uv sync --extra tcptrace --extra ndt7 # Установите pre-commit хуки @@ -28,10 +28,10 @@ uv run pre-commit install uv run pytest # Тесты с покрытием -uv run pytest --cov=src/netsleuth --cov-report=term-missing +uv run pytest --cov=src/netsleuth --cov-report=html # Конкретный тест -uv run pytest tests/test_cli.py -v +uv run pytest tests/test_exporter.py -v ``` ### 3. Линтинг и форматирование @@ -40,290 +40,114 @@ uv run pytest tests/test_cli.py -v # Проверка стиля uv run flake8 src/netsleuth -# Авто-форматирование (ruff) -uv run ruff format src/netsleuth +# Форматирование кода +uv run black src/netsleuth tests # Проверка типов -uv run mypy src/netsleuth +uv run mypy src/netsleuth --ignore-missing-imports ``` ---- +## 📝 Как добавить новый источник геоданных -## 📁 Структура проекта - -``` -netsleuth/ -├── src/netsleuth/ # Исходный код -│ ├── cli.py # CLI интерфейс (typer) -│ ├── config.py # Конфигурация и настройки -│ ├── models.py # Датаклассы и модели данных -│ ├── bgp.py # BGP и ASN информация -│ ├── geoip.py # Геолокация IP -│ ├── latency.py # Измерение задержек -│ ├── path.py # Traceroute и анализ пути -│ ├── speed.py # Speedtest и bufferbloat -│ ├── reputation.py # Проверка IP репутации -│ ├── exporter.py # Генерация отчётов -│ └── interpret.py # Интерпретация результатов -├── tests/ # Тесты -├── .github/workflows/ # CI/CD пайплайны -└── docs/ # Документация -``` - ---- - -## 🔧 Как добавить новый источник геоданных - -### Пример: Добавление GeoIP провайдера - -1. **Создайте функцию в `geoip.py`:** +1. Создайте новый файл в `src/netsleuth/geo/` (например, `new_provider.py`) +2. Реализуйте интерфейс: ```python -async def fetch_geolocation_new_provider(ip: str) -> dict[str, Any]: - """Получает геоданные от нового провайдера. - - Args: - ip: IP адрес для поиска - - Returns: - Dict с ключами: country, city, org, asn - - Raises: - httpx.HTTPError: Если API недоступен - """ - async with httpx.AsyncClient() as client: - response = await client.get( - f"https://api.newprovider.com/geo/{ip}", - timeout=5.0 - ) - response.raise_for_status() - return parse_geolocation_response(response.json()) +from typing import Optional +from dataclasses import dataclass + +@dataclass +class GeoResult: + country: Optional[str] = None + city: Optional[str] = None + asn: Optional[int] = None + org: Optional[str] = None + +async def fetch_geo(ip: str) -> Optional[GeoResult]: + """Получить геоданные для IP.""" + # Ваша реализация + pass ``` -2. **Добавьте парсер ответа:** +3. Добавьте импорт в `src/netsleuth/geo/__init__.py` +4. Напишите тесты в `tests/test_new_provider.py` +5. Обновите документацию -```python -def parse_geolocation_response(payload: dict) -> dict[str, Any]: - """Извлекает поля из ответа API.""" - return { - "country": payload.get("country_name"), - "city": payload.get("city"), - "org": payload.get("org"), - "asn": payload.get("asn"), - } -``` - -3. **Интегрируйте в основной пайплайн:** - -В `cli.py` найдите `gather_identity()` и добавьте вызов: - -```python -geo_new = await fetch_geolocation_new_provider(ip) -``` - -4. **Напишите тест:** - -Создайте `tests/test_geoip_new_provider.py`: +## 🧪 Как написать тест для новой пробы ```python import pytest from unittest.mock import AsyncMock, patch @pytest.mark.asyncio -async def test_fetch_geolocation_new_provider(httpx_mock): - httpx_mock.add_response( - url="https://api.newprovider.com/geo/8.8.8.8", - json={"country_name": "US", "city": "Mountain View"} - ) - - from netsleuth.geoip import fetch_geolocation_new_provider - result = await fetch_geolocation_new_provider("8.8.8.8") - - assert result["country"] == "US" - assert result["city"] == "Mountain View" -``` - ---- - -## 🧪 Как написать тест для новой пробы - -### Шаблон теста - -```python -"""Тесты для модуля .""" - -import pytest -from unittest.mock import patch, MagicMock - -from netsleuth. import - - -class TestFunctionName: - """Тесты для .""" - - @pytest.mark.asyncio - async def test_success_case(self, httpx_mock): - """Тестирует успешный сценарий.""" - # Arrange - httpx_mock.add_response(...) +async def test_my_probe(): + # Arrange + with patch('netsleuth.my_probe.httpx.get') as mock_get: + mock_get.return_value = {"status": "ok"} # Act - result = await (...) + result = await run_probe("8.8.8.8") # Assert - assert result.expected_field == "expected_value" - - @pytest.mark.asyncio - async def test_error_handling(self): - """Тестирует обработку ошибок.""" - with pytest.raises(ExpectedError): - await (invalid_input) - - @pytest.mark.asyncio - async def test_edge_case_empty_input(self): - """Тестирует граничный случай.""" - result = await ("") - assert result is None + assert result.status == "ok" + mock_get.assert_called_once() ``` -### Мокирование HTTP запросов - -```python -@pytest.mark.asyncio -async def test_with_httpx_mock(httpx_mock): - """httpx_mock автоматически мокирует httpx запросы.""" - - # Добавляем ожидаемый запрос - httpx_mock.add_response( - url="https://api.example.com/data", - method="GET", - status_code=200, - json={"key": "value"} - ) - - # Вызываем функцию - result = await fetch_data() - - # Проверяем что запрос был сделан - request = httpx_mock.get_request() - assert request.url == "https://api.example.com/data" -``` - ---- +**Важно:** +- Все HTTP-запросы должны быть замокированы +- Используйте фикстуры из `tests/conftest.py` +- Добавляйте тесты на граничные случаи ## ✅ Чеклист перед Pull Request -Перед отправкой PR убедитесь: - -- [ ] Все тесты проходят: `uv run pytest` +- [ ] Код отформатирован (`black src/netsleuth tests`) +- [ ] Нет нарушений flake8 (`flake8 src/netsleuth`) +- [ ] Все тесты проходят (`pytest`) - [ ] Покрытие не уменьшилось (минимум 60%) -- [ ] Линтер не находит ошибок: `uv run flake8 src/netsleuth` -- [ ] Типизация корректна: `uv run mypy src/netsleuth` -- [ ] Код отформатирован: `uv run ruff format src/netsleuth` - [ ] Добавлены docstrings для публичных функций -- [ ] Добавлены тесты для нового функционала - [ ] Обновлена документация (если нужно) -- [ ] Pre-commit хуки проходят: `uv run pre-commit run --all-files` +- [ ] Pre-commit хуки пройдены ---- +## 🏗️ Архитектура проекта -## 📝 Стиль кода - -### Именование - -- **Функции**: `snake_case` (`fetch_geolocation`) -- **Классы**: `PascalCase` (`SpeedResult`) -- **Константы**: `UPPER_CASE` (`MAX_RETRIES`) -- **Приватные функции**: `_prefix` (`_parse_response`) - -### Docstrings - -Используйте Google style: - -```python -def calculate_latency(samples: list[float]) -> float: - """Вычисляет среднюю задержку из списка семплов. - - Args: - samples: Список задержек в миллисекундах - - Returns: - Средняя задержка, или 0.0 если список пуст - - Raises: - ValueError: Если есть отрицательные значения - """ - if not samples: - return 0.0 - - if any(s < 0 for s in samples): - raise ValueError("Latency cannot be negative") - - return sum(samples) / len(samples) +``` +src/netsleuth/ +├── cli.py # Точка входа, CLI аргументы +├── config.py # Конфигурация и настройки +├── models.py # Датаклассы для данных +├── probes/ # Модули проб (speed, latency, etc.) +├── geo/ # Геоданные и ASN lookup +├── reputation.py # Проверка по чёрным спискам +├── interpret.py # Интерпретация результатов +└── exporter.py # Экспорт в Markdown/JSON ``` -### Type hints +Основная статья: [`ARCHITECTURE.ru.md`](ARCHITECTURE.ru.md) -Все публичные функции должны иметь аннотации типов: +## 🐛 Сообщение об ошибках -```python -from typing import Any, Optional +При создании issue укажите: +1. Версию Python (`python --version`) +2. Версию netsleuth (`netsleuth --version`) +3. ОС и версию +4. Команду запуска +5. Полный вывод ошибки +6. Скриншот (если применимо) -def process_ip(ip: str, timeout: float = 5.0) -> Optional[dict[str, Any]]: - ... -``` +## 💡 Идеи для улучшений + +Ищем помощь в следующих областях: +- [ ] Поддержка новых протоколов (QUIC, HTTP/3) +- [ ] Графический интерфейс (TUI/GUI) +- [ ] Интеграция с Prometheus/Grafana +- [ ] Поддержка IPv6-only хостов +- [ ] Локализация на другие языки + +## 📞 Контакты + +- GitHub Issues: [egorovandrey/netsleuth/issues](https://github.com/egorovandrey/netsleuth/issues) +- Telegram: @your_channel (если есть) --- -## 🐛 Отладка - -### Логирование - -Включите дебаг логирование: - -```bash -netsleuth --watch --verbose -``` - -### Отладка тестов - -```bash -# Запустить с выводом print() -uv run pytest -s tests/test_file.py::test_name - -# Остановиться на первой ошибке -uv run pytest -x tests/ - -# Запустить последний упавший тест -uv run pytest --lf -``` - ---- - -## 📞 Вопросы и поддержка - -- **GitHub Issues**: Для багов и фич https://github.com/netsleuth/netsleuth/issues -- **Discussions**: Для вопросов https://github.com/netsleuth/netsleuth/discussions - ---- - -## 🎯 Идеи для контрибьюшена - -### Начинающим - -- [ ] Добавить docstrings к функциям без документации -- [ ] Исправить опечатки в README -- [ ] Добавить тесты для существующих функций -- [ ] Улучшить сообщения об ошибках - -### Продвинутым - -- [ ] Добавить новый источник геоданных -- [ ] Реализовать кэширование API запросов -- [ ] Оптимизировать производительность NetsetIndex -- [ ] Добавить поддержку IPv6 в traceroute -- [ ] Создать GUI для результатов - ---- - -Спасибо за ваш вклад! 🎉 +**Спасибо за ваш вклад!** 🎉