mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-09-19 14:14:22 +00:00
Integration fixes on top of the merge, in three groups. Regressions in the shared shortener, which is not opt-in — weather alerts call it unconditionally whenever an alert carries a link: - Restore the `response.ok` guard on the v.gd path. Without it a 503 whose body happens to start with `http` (a captive portal, a CDN error page) was returned as the short URL. The test that covered this still passed because it never set `.text`, so `MagicMock.startswith()` answered truthily; it now supplies a body that would be accepted on a 200, so only the status check can make it pass. - Add the same guard to the shlink path, where a rejected API key is a 401 with a problem-details body and was indistinguishable from an empty result. - Restore debug-level logging for `Timeout`/`ConnectionError`. A mesh node's uplink drops out routinely and this had become `logger.error`. - Drop the `shortUrlSlug` fallback. Shlink's create response carries `shortUrl` and `shortCode`; `shortUrlSlug` is not in its schema, and a bare slug is not a link — emitting one would have put `abc123` in a message. The `shorten_url` filter did blocking HTTP inside the synchronous render path (`process_message` → `check_keywords` → `format_response`), so a 5 s shortener timeout stalled the radio transport along with everything else. Making `check_keywords` async would touch 27 call sites, so instead the network work moves ahead of the render: - `resolve_template_async()` renders once with the filter in collection mode, which walks the real chain so a clause already suppressed by `hops_min` costs no request, then shortens what survived concurrently off-thread. - The filter itself now only reads that mapping and never calls out. Asked to render without a pre-pass it warns and drops the clause rather than block. - On failure the clause is dropped rather than sent unshortened. A v.gd link is ~19 bytes where the analyzer URL is ~59, against a ~158-byte budget the prefix is subtracted from, so falling back would have turned one transmission into two whenever the shortener was unreachable. Smaller: rename `if_notempty` to `if_nonempty` to match the existing `prefix_if_nonempty` (nothing ships using it yet, so there is no config to migrate); `_build_create_shlink_url` no longer takes the long URL and API key it never used; move the CHANGELOG entry from Fixed to Added and name the filter it actually added; ungate the render debug log from `config`; document that `shorten_url` is supported in `path`'s `reply_prefix` only. Every template shipped in config.ini.example still renders identically to the regex parser it replaced.
MeshCore Bot Test Suite
This directory contains the test suite for the MeshCore Bot, focusing on graph-based path guessing functionality.
Structure
tests/
├── README.md # This file
├── conftest.py # Pytest fixtures and configuration
├── helpers.py # Test data factories and helper functions
├── unit/ # Unit tests (isolated, with mocks)
│ ├── test_mesh_graph_edges.py
│ ├── test_mesh_graph_validation.py
│ ├── test_mesh_graph_scoring.py
│ ├── test_mesh_graph_multihop.py
│ └── test_path_command_graph_selection.py
└── integration/ # Integration tests (with real database)
└── test_path_resolution.py
Running Tests
Run all tests
pytest
Run only unit tests
pytest tests/unit/
Run only integration tests
pytest tests/integration/
Run specific test file
pytest tests/unit/test_mesh_graph_edges.py
Run specific test
pytest tests/unit/test_mesh_graph_edges.py::TestMeshGraphEdges::test_add_new_edge
Run with coverage
pytest --cov=modules --cov-report=html --cov-report=term-missing
Run with verbose output
pytest -v
Run with markers
pytest -m unit # Run only unit tests
pytest -m integration # Run only integration tests
pytest -m slow # Run slow tests
Test Coverage
Unit Tests
test_mesh_graph_edges.py (15 tests)
Tests for MeshGraph edge management:
- Adding new edges
- Updating existing edges
- Public key handling
- Hop position tracking
- Geographic distance
- Edge queries (get, has, outgoing, incoming)
- Prefix normalization
test_mesh_graph_validation.py (12 tests)
Tests for path validation:
- Path segment validation
- Confidence calculation
- Recency checks
- Bidirectional edge validation
- Full path validation
- Minimum observations filtering
test_mesh_graph_scoring.py (11 tests)
Tests for candidate scoring:
- Score calculation with various edge combinations
- Bidirectional bonuses
- Hop position matching
- Geographic distance bonuses
- Minimum observations filtering
test_mesh_graph_multihop.py (12 tests)
Tests for multi-hop path inference:
- 2-hop and 3-hop path finding
- Intermediate node discovery
- Minimum observations filtering
- Bidirectional path bonuses
- Score reduction for longer paths
test_path_command_graph_selection.py (8 tests)
Tests for PathCommand._select_repeater_by_graph:
- Direct edge selection
- Stored public key bonus
- Star bias multiplier
- Multi-hop inference
- Confidence conversion
Integration Tests
test_path_resolution.py (5 tests)
End-to-end tests for full path resolution:
- Path resolution with graph edges from database
- Prefix collision resolution using graph data
- Edge persistence across graph restarts
- Graph vs geographic selection
- Real-world multi-hop scenarios
Test Fixtures
Fixtures are defined in conftest.py:
mock_logger: Mock logger for testingtest_config: Test configuration with Path_Command settingstest_db: In-memory SQLite database for testingmock_bot: Mock bot instance with all necessary attributesmesh_graph: CleanMeshGraphinstance for testingpopulated_mesh_graph:MeshGraphinstance with sample edges
Test Helpers
Helper functions in helpers.py:
create_test_repeater(): Factory for creating test repeater datacreate_test_edge(): Factory for creating test edge datacreate_test_path(): Factory for creating test path datapopulate_test_graph(): Helper to populate a graph with test edges
Writing New Tests
Unit Test Example
import pytest
from tests.helpers import create_test_edge
@pytest.mark.unit
class TestMyFeature:
def test_my_feature(self, mesh_graph):
"""Test description."""
mesh_graph.add_edge('01', '7e')
assert mesh_graph.has_edge('01', '7e')
Integration Test Example
import pytest
@pytest.mark.integration
class TestMyIntegration:
def test_my_integration(self, mock_bot, test_db):
"""Test description."""
# Use real database and bot components
pass
Test Markers
Tests are marked with:
@pytest.mark.unit: Unit tests (isolated, with mocks)@pytest.mark.integration: Integration tests (with real database)@pytest.mark.slow: Slow-running tests
Dependencies
Test dependencies are in requirements.txt:
pytest>=7.0.0pytest-asyncio>=0.21.0pytest-mock>=3.10.0pytest-cov>=4.0.0
Configuration
Pytest configuration is in pytest.ini:
- Test discovery patterns
- Async test support
- Output options
- Test markers
Notes
- Unit tests use in-memory SQLite databases for speed
- Integration tests may use real database connections
- All tests should be deterministic and not depend on external services
- Tests should clean up after themselves (fixtures handle this automatically)