mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-11-04 11:57:16 +00:00
A space bunny review of this branch's commits found:
- gwx's high-wind warning converted the speed to mph for every catalog, so on a km/h bot it read 49 next to a reply saying 80, and Polish (whose template said km/h) regressed in the merge. The threshold stays 30 mph; the warning shows the speed in the configured unit with a {unit} placeholder in every catalog.
- tomorrow_not_available, tomorrow_error, multiday_not_available and multiday_error were still English in seven catalogs; they are translated, and the catalog test now fails on any weather reply left in English.
- wx kept NOAA's English wind letters and English weekday letters while gwx's are translated, so one German bot showed NE and NO for the same wind. wx now translates both through the catalog (commands.wx.day_abbrev added); English output is byte-for-byte unchanged, which the goldens show (their stub translator gives English for these short labels, via tests.helpers.key_name_translate).
- Polish feels_like was ungrammatical (odczuwalna -> odczuwalnie), Russian's used a nonstandard abbreviation, and Russian gwx's MQTT subscriber error lacked wx's hint.
- The help text (commands.wx.description/usage, commands.gwx.usage) described wx as ZIP-only; it now lists cities, coordinates and the options, under 130 bytes in every language.
- The generated default config, the docs' default_state wording, _fit_tomorrow_parts' docstring and the CHANGELOG's stats claim were inaccurate.
183 lines
6.6 KiB
Python
183 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test helper functions and factories for creating test data
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Optional
|
|
|
|
|
|
def create_test_repeater(
|
|
prefix: str = "01",
|
|
name: str = "Test Repeater",
|
|
public_key: Optional[str] = None,
|
|
latitude: float = 47.6062,
|
|
longitude: float = -122.3321,
|
|
is_starred: bool = False,
|
|
last_heard: Optional[datetime] = None,
|
|
last_advert_timestamp: Optional[datetime] = None,
|
|
role: str = "repeater"
|
|
) -> dict[str, Any]:
|
|
"""Factory function to create test repeater data.
|
|
|
|
Args:
|
|
prefix: Two-character hex prefix (default: "01")
|
|
name: Repeater name
|
|
public_key: Full public key (default: prefix repeated 32 times)
|
|
latitude: Latitude coordinate
|
|
longitude: Longitude coordinate
|
|
is_starred: Whether repeater is starred
|
|
last_heard: Last heard timestamp (default: now)
|
|
last_advert_timestamp: Last advert timestamp (default: now)
|
|
role: Device role (default: "repeater")
|
|
|
|
Returns:
|
|
Dictionary with repeater data matching database schema
|
|
"""
|
|
if public_key is None:
|
|
# Generate a realistic-looking public key from prefix
|
|
public_key = (prefix.lower() * 16)[:64] # 64 hex chars = 32 bytes
|
|
|
|
now = datetime.now()
|
|
if last_heard is None:
|
|
last_heard = now
|
|
if last_advert_timestamp is None:
|
|
last_advert_timestamp = now
|
|
|
|
return {
|
|
'name': name,
|
|
'public_key': public_key,
|
|
'device_type': 'repeater',
|
|
'last_seen': last_heard,
|
|
'last_heard': last_heard,
|
|
'last_advert_timestamp': last_advert_timestamp,
|
|
'is_active': True,
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'city': 'Seattle',
|
|
'state': 'WA',
|
|
'country': 'USA',
|
|
'advert_count': 1,
|
|
'signal_strength': None,
|
|
'hop_count': 0,
|
|
'role': role,
|
|
'is_starred': is_starred
|
|
}
|
|
|
|
|
|
def create_test_edge(
|
|
from_prefix: str,
|
|
to_prefix: str,
|
|
from_public_key: Optional[str] = None,
|
|
to_public_key: Optional[str] = None,
|
|
observation_count: int = 1,
|
|
first_seen: Optional[datetime] = None,
|
|
last_seen: Optional[datetime] = None,
|
|
avg_hop_position: Optional[float] = None,
|
|
geographic_distance: Optional[float] = None,
|
|
prefix_hex_chars: int = 2
|
|
) -> dict[str, Any]:
|
|
"""Factory function to create test edge data.
|
|
|
|
Args:
|
|
from_prefix: Source node prefix
|
|
to_prefix: Destination node prefix
|
|
from_public_key: Source public key (default: generated from prefix)
|
|
to_public_key: Destination public key (default: generated from prefix)
|
|
observation_count: Number of times edge observed
|
|
first_seen: First observation time (default: now)
|
|
last_seen: Last observation time (default: now)
|
|
avg_hop_position: Average hop position in paths
|
|
geographic_distance: Distance in km
|
|
prefix_hex_chars: Number of hex chars per prefix (default 2). Use bot.prefix_hex_chars when testing with a bot.
|
|
|
|
Returns:
|
|
Dictionary with edge data matching MeshGraph edge structure
|
|
"""
|
|
now = datetime.now()
|
|
if first_seen is None:
|
|
first_seen = now
|
|
if last_seen is None:
|
|
last_seen = now
|
|
|
|
if from_public_key is None:
|
|
from_public_key = (from_prefix.lower() * 16)[:64]
|
|
if to_public_key is None:
|
|
to_public_key = (to_prefix.lower() * 16)[:64]
|
|
|
|
return {
|
|
'from_prefix': from_prefix.lower()[:prefix_hex_chars],
|
|
'to_prefix': to_prefix.lower()[:prefix_hex_chars],
|
|
'from_public_key': from_public_key,
|
|
'to_public_key': to_public_key,
|
|
'observation_count': observation_count,
|
|
'first_seen': first_seen,
|
|
'last_seen': last_seen,
|
|
'avg_hop_position': avg_hop_position,
|
|
'geographic_distance': geographic_distance
|
|
}
|
|
|
|
|
|
def create_test_path(node_ids: list[str], prefix_hex_chars: int = 2) -> list[str]:
|
|
"""Factory function to create test path data.
|
|
|
|
Args:
|
|
node_ids: List of node prefixes in path order
|
|
prefix_hex_chars: Number of hex chars per node (default 2). Use bot.prefix_hex_chars when testing with a bot.
|
|
|
|
Returns:
|
|
List of node IDs (normalized to lowercase)
|
|
"""
|
|
return [node_id.lower()[:prefix_hex_chars] for node_id in node_ids]
|
|
|
|
|
|
def populate_test_graph(mesh_graph, edges: list[dict[str, Any]], prefix_hex_chars: int = 2):
|
|
"""Helper to populate a MeshGraph instance with test edges.
|
|
|
|
Args:
|
|
mesh_graph: MeshGraph instance to populate
|
|
edges: List of edge dictionaries (from create_test_edge)
|
|
prefix_hex_chars: Number of hex chars per prefix (default 2). Must match mesh_graph's bot.prefix_hex_chars when graph uses prefix-based keys.
|
|
"""
|
|
for edge in edges:
|
|
mesh_graph.add_edge(
|
|
edge['from_prefix'],
|
|
edge['to_prefix'],
|
|
from_public_key=edge.get('from_public_key'),
|
|
to_public_key=edge.get('to_public_key'),
|
|
hop_position=edge.get('avg_hop_position'),
|
|
geographic_distance=edge.get('geographic_distance')
|
|
)
|
|
# Manually set observation_count and timestamps if needed
|
|
edge_key = (edge['from_prefix'].lower()[:prefix_hex_chars], edge['to_prefix'].lower()[:prefix_hex_chars])
|
|
if edge_key in mesh_graph.edges:
|
|
if edge.get('observation_count', 1) > 1:
|
|
mesh_graph.edges[edge_key]['observation_count'] = edge['observation_count']
|
|
if edge.get('first_seen'):
|
|
mesh_graph.edges[edge_key]['first_seen'] = edge['first_seen']
|
|
if edge.get('last_seen'):
|
|
mesh_graph.edges[edge_key]['last_seen'] = edge['last_seen']
|
|
|
|
|
|
_ENGLISH_CATALOG: dict[str, Any] | None = None
|
|
# Short labels inside weather replies; stub translators show them in English so that
|
|
# reply lengths (and byte budgets) stay those of a real English bot.
|
|
_ENGLISH_LABEL_PREFIXES = ("common.wind_directions.", "commands.wx.day_abbrev.")
|
|
|
|
|
|
def key_name_translate(key: str, **kwargs: Any) -> str:
|
|
"""A stub translation: ``<key[kwargs]>``, except short weather labels, which are English."""
|
|
global _ENGLISH_CATALOG
|
|
if key.startswith(_ENGLISH_LABEL_PREFIXES):
|
|
if _ENGLISH_CATALOG is None:
|
|
import json
|
|
from pathlib import Path
|
|
|
|
path = Path(__file__).resolve().parents[1] / "translations" / "en.json"
|
|
_ENGLISH_CATALOG = json.loads(path.read_text(encoding="utf-8"))
|
|
node: Any = _ENGLISH_CATALOG
|
|
for part in key.split("."):
|
|
node = node[part]
|
|
return node
|
|
return f"<{key}{sorted(kwargs.items()) or ''}>"
|