mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
Split up community broker fields and reformat MQTT config page
This commit is contained in:
@@ -370,10 +370,10 @@ Separate from private MQTT, the community publisher (`app/community_mqtt.py`) sh
|
||||
|
||||
- Connects to community broker (default `mqtt-us-v1.letsmesh.net:443`) via WebSockets over TLS.
|
||||
- Authentication via Ed25519 JWT signed with the radio's private key. Tokens auto-renew before 24h expiry.
|
||||
- Broker address field supports `host:port` format (default port 443 if omitted).
|
||||
- Broker address: separate `community_mqtt_broker_host` and `community_mqtt_broker_port` fields; defaults to `mqtt-us-v1.letsmesh.net:443`.
|
||||
- Topic: `meshcore/{IATA}/{pubkey}/packets` — IATA is a 3-letter region code.
|
||||
- JWT `email` claim enables node claiming on the community aggregator.
|
||||
- Config: `community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker`, `community_mqtt_email` in `app_settings`.
|
||||
- Config: `community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker_host`, `community_mqtt_broker_port`, `community_mqtt_email` in `app_settings`.
|
||||
|
||||
### Server-Side Decryption
|
||||
|
||||
@@ -416,7 +416,7 @@ mc.subscribe(EventType.ACK, handler)
|
||||
| `MESHCORE_LOG_LEVEL` | `INFO` | Logging level (`DEBUG`/`INFO`/`WARNING`/`ERROR`) |
|
||||
| `MESHCORE_DATABASE_PATH` | `data/meshcore.db` | SQLite database location |
|
||||
|
||||
**Note:** Runtime app settings are stored in the database (`app_settings` table), not environment variables. These include `max_radio_contacts`, `auto_decrypt_dm_on_advert`, `sidebar_sort_order`, `advert_interval`, `last_advert_time`, `favorites`, `last_message_times`, `bots`, all MQTT configuration (`mqtt_broker_host`, `mqtt_broker_port`, `mqtt_username`, `mqtt_password`, `mqtt_use_tls`, `mqtt_tls_insecure`, `mqtt_topic_prefix`, `mqtt_publish_messages`, `mqtt_publish_raw_packets`), and community MQTT configuration (`community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker`, `community_mqtt_email`). They are configured via `GET/PATCH /api/settings` (and related settings endpoints).
|
||||
**Note:** Runtime app settings are stored in the database (`app_settings` table), not environment variables. These include `max_radio_contacts`, `auto_decrypt_dm_on_advert`, `sidebar_sort_order`, `advert_interval`, `last_advert_time`, `favorites`, `last_message_times`, `bots`, all MQTT configuration (`mqtt_broker_host`, `mqtt_broker_port`, `mqtt_username`, `mqtt_password`, `mqtt_use_tls`, `mqtt_tls_insecure`, `mqtt_topic_prefix`, `mqtt_publish_messages`, `mqtt_publish_raw_packets`), and community MQTT configuration (`community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker_host`, `community_mqtt_broker_port`, `community_mqtt_email`). They are configured via `GET/PATCH /api/settings` (and related settings endpoints).
|
||||
|
||||
Byte-perfect channel retries are user-triggered via `POST /api/messages/channel/{message_id}/resend` and are allowed for 30 seconds after the original send.
|
||||
|
||||
|
||||
@@ -122,13 +122,13 @@ app/
|
||||
- Independent from the private `MqttPublisher` — different broker, authentication, and topic structure.
|
||||
- Connects to the community broker (default `mqtt-us-v1.letsmesh.net:443`) via WebSockets over TLS.
|
||||
- Authentication: Ed25519 JWT tokens signed with the radio's expanded "orlp" private key. Tokens expire after 24 hours; proactive renewal at 23 hours.
|
||||
- Broker address supports `host:port` format; defaults to port 443 if omitted.
|
||||
- Broker address: separate `community_mqtt_broker_host` and `community_mqtt_broker_port` fields; defaults to `mqtt-us-v1.letsmesh.net:443`.
|
||||
- JWT claims include `publicKey`, `owner` (radio pubkey), `client` (app identifier), and optional `email` (for node claiming on the community aggregator).
|
||||
- Topic: `meshcore/{IATA}/{pubkey}/packets` — IATA is a 3-letter region code (required to enable; no default).
|
||||
- Only raw packets are published — never decrypted messages.
|
||||
- Publishes are fire-and-forget. The connection loop detects publish failures via `connected` flag and reconnects within 60 seconds.
|
||||
- Health endpoint includes `community_mqtt_status` field.
|
||||
- Settings: `community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker`, `community_mqtt_email`.
|
||||
- Settings: `community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker_host`, `community_mqtt_broker_port`, `community_mqtt_email`.
|
||||
|
||||
## API Surface (all under `/api`)
|
||||
|
||||
@@ -238,7 +238,7 @@ Main tables:
|
||||
- `bots`
|
||||
- `mqtt_broker_host`, `mqtt_broker_port`, `mqtt_username`, `mqtt_password`
|
||||
- `mqtt_use_tls`, `mqtt_tls_insecure`, `mqtt_topic_prefix`, `mqtt_publish_messages`, `mqtt_publish_raw_packets`
|
||||
- `community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker`, `community_mqtt_email`
|
||||
- `community_mqtt_enabled`, `community_mqtt_iata`, `community_mqtt_broker_host`, `community_mqtt_broker_port`, `community_mqtt_email`
|
||||
|
||||
## Security Posture (intentional)
|
||||
|
||||
|
||||
@@ -43,17 +43,6 @@ _IATA_RE = re.compile(r"^[A-Z]{3}$")
|
||||
_ROUTE_MAP = {0: "F", 1: "F", 2: "D", 3: "T"}
|
||||
|
||||
|
||||
def _parse_broker_address(broker_str: str) -> tuple[str, int]:
|
||||
"""Parse 'host' or 'host:port' into (host, port). Defaults to _DEFAULT_PORT."""
|
||||
if ":" in broker_str:
|
||||
host, port_str = broker_str.rsplit(":", 1)
|
||||
try:
|
||||
return host, int(port_str)
|
||||
except ValueError:
|
||||
pass
|
||||
return broker_str, _DEFAULT_PORT
|
||||
|
||||
|
||||
def _base64url_encode(data: bytes) -> str:
|
||||
"""Base64url encode without padding."""
|
||||
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
|
||||
@@ -308,8 +297,8 @@ class CommunityMqttPublisher(BaseMqttPublisher):
|
||||
assert private_key is not None and public_key is not None # guaranteed by _pre_connect
|
||||
|
||||
pubkey_hex = public_key.hex().upper()
|
||||
broker_raw = settings.community_mqtt_broker or _DEFAULT_BROKER
|
||||
broker_host, broker_port = _parse_broker_address(broker_raw)
|
||||
broker_host = settings.community_mqtt_broker_host or _DEFAULT_BROKER
|
||||
broker_port = settings.community_mqtt_broker_port or _DEFAULT_PORT
|
||||
jwt_token = _generate_jwt_token(
|
||||
private_key,
|
||||
public_key,
|
||||
@@ -330,8 +319,8 @@ class CommunityMqttPublisher(BaseMqttPublisher):
|
||||
}
|
||||
|
||||
def _on_connected(self, settings: AppSettings) -> tuple[str, str]:
|
||||
broker_raw = settings.community_mqtt_broker or _DEFAULT_BROKER
|
||||
broker_host, broker_port = _parse_broker_address(broker_raw)
|
||||
broker_host = settings.community_mqtt_broker_host or _DEFAULT_BROKER
|
||||
broker_port = settings.community_mqtt_broker_port or _DEFAULT_PORT
|
||||
return ("Community MQTT connected", f"{broker_host}:{broker_port}")
|
||||
|
||||
def _on_error(self) -> tuple[str, str]:
|
||||
|
||||
@@ -1916,7 +1916,8 @@ async def _migrate_032_add_community_mqtt_columns(conn: aiosqlite.Connection) ->
|
||||
new_columns = [
|
||||
("community_mqtt_enabled", "INTEGER DEFAULT 0"),
|
||||
("community_mqtt_iata", "TEXT DEFAULT ''"),
|
||||
("community_mqtt_broker", "TEXT DEFAULT 'mqtt-us-v1.letsmesh.net'"),
|
||||
("community_mqtt_broker_host", "TEXT DEFAULT 'mqtt-us-v1.letsmesh.net'"),
|
||||
("community_mqtt_broker_port", "INTEGER DEFAULT 443"),
|
||||
("community_mqtt_email", "TEXT DEFAULT ''"),
|
||||
]
|
||||
|
||||
|
||||
@@ -471,10 +471,14 @@ class AppSettings(BaseModel):
|
||||
default="",
|
||||
description="IATA region code for community MQTT topic routing (3 alpha chars)",
|
||||
)
|
||||
community_mqtt_broker: str = Field(
|
||||
community_mqtt_broker_host: str = Field(
|
||||
default="mqtt-us-v1.letsmesh.net",
|
||||
description="Community MQTT broker hostname",
|
||||
)
|
||||
community_mqtt_broker_port: int = Field(
|
||||
default=443,
|
||||
description="Community MQTT broker port",
|
||||
)
|
||||
community_mqtt_email: str = Field(
|
||||
default="",
|
||||
description="Email address for node claiming on the community aggregator (optional)",
|
||||
|
||||
@@ -31,7 +31,8 @@ class AppSettingsRepository:
|
||||
mqtt_use_tls, mqtt_tls_insecure, mqtt_topic_prefix,
|
||||
mqtt_publish_messages, mqtt_publish_raw_packets,
|
||||
community_mqtt_enabled, community_mqtt_iata,
|
||||
community_mqtt_broker, community_mqtt_email
|
||||
community_mqtt_broker_host, community_mqtt_broker_port,
|
||||
community_mqtt_email
|
||||
FROM app_settings WHERE id = 1
|
||||
"""
|
||||
)
|
||||
@@ -107,7 +108,9 @@ class AppSettingsRepository:
|
||||
mqtt_publish_raw_packets=bool(row["mqtt_publish_raw_packets"]),
|
||||
community_mqtt_enabled=bool(row["community_mqtt_enabled"]),
|
||||
community_mqtt_iata=row["community_mqtt_iata"] or "",
|
||||
community_mqtt_broker=row["community_mqtt_broker"] or "mqtt-us-v1.letsmesh.net",
|
||||
community_mqtt_broker_host=row["community_mqtt_broker_host"]
|
||||
or "mqtt-us-v1.letsmesh.net",
|
||||
community_mqtt_broker_port=row["community_mqtt_broker_port"] or 443,
|
||||
community_mqtt_email=row["community_mqtt_email"] or "",
|
||||
)
|
||||
|
||||
@@ -133,7 +136,8 @@ class AppSettingsRepository:
|
||||
mqtt_publish_raw_packets: bool | None = None,
|
||||
community_mqtt_enabled: bool | None = None,
|
||||
community_mqtt_iata: str | None = None,
|
||||
community_mqtt_broker: str | None = None,
|
||||
community_mqtt_broker_host: str | None = None,
|
||||
community_mqtt_broker_port: int | None = None,
|
||||
community_mqtt_email: str | None = None,
|
||||
) -> AppSettings:
|
||||
"""Update app settings. Only provided fields are updated."""
|
||||
@@ -222,9 +226,13 @@ class AppSettingsRepository:
|
||||
updates.append("community_mqtt_iata = ?")
|
||||
params.append(community_mqtt_iata)
|
||||
|
||||
if community_mqtt_broker is not None:
|
||||
updates.append("community_mqtt_broker = ?")
|
||||
params.append(community_mqtt_broker)
|
||||
if community_mqtt_broker_host is not None:
|
||||
updates.append("community_mqtt_broker_host = ?")
|
||||
params.append(community_mqtt_broker_host)
|
||||
|
||||
if community_mqtt_broker_port is not None:
|
||||
updates.append("community_mqtt_broker_port = ?")
|
||||
params.append(community_mqtt_broker_port)
|
||||
|
||||
if community_mqtt_email is not None:
|
||||
updates.append("community_mqtt_email = ?")
|
||||
|
||||
@@ -106,10 +106,16 @@ class AppSettingsUpdate(BaseModel):
|
||||
default=None,
|
||||
description="IATA region code for community MQTT topic routing (3 alpha chars)",
|
||||
)
|
||||
community_mqtt_broker: str | None = Field(
|
||||
community_mqtt_broker_host: str | None = Field(
|
||||
default=None,
|
||||
description="Community MQTT broker hostname",
|
||||
)
|
||||
community_mqtt_broker_port: int | None = Field(
|
||||
default=None,
|
||||
ge=1,
|
||||
le=65535,
|
||||
description="Community MQTT broker port",
|
||||
)
|
||||
community_mqtt_email: str | None = Field(
|
||||
default=None,
|
||||
description="Email address for node claiming on the community aggregator",
|
||||
@@ -214,8 +220,12 @@ async def update_settings(update: AppSettingsUpdate) -> AppSettings:
|
||||
kwargs["community_mqtt_iata"] = iata
|
||||
community_mqtt_changed = True
|
||||
|
||||
if update.community_mqtt_broker is not None:
|
||||
kwargs["community_mqtt_broker"] = update.community_mqtt_broker
|
||||
if update.community_mqtt_broker_host is not None:
|
||||
kwargs["community_mqtt_broker_host"] = update.community_mqtt_broker_host
|
||||
community_mqtt_changed = True
|
||||
|
||||
if update.community_mqtt_broker_port is not None:
|
||||
kwargs["community_mqtt_broker_port"] = update.community_mqtt_broker_port
|
||||
community_mqtt_changed = True
|
||||
|
||||
if update.community_mqtt_email is not None:
|
||||
|
||||
@@ -30,7 +30,8 @@ export function SettingsMqttSection({
|
||||
// Community MQTT state
|
||||
const [communityMqttEnabled, setCommunityMqttEnabled] = useState(false);
|
||||
const [communityMqttIata, setCommunityMqttIata] = useState('');
|
||||
const [communityMqttBroker, setCommunityMqttBroker] = useState('mqtt-us-v1.letsmesh.net');
|
||||
const [communityMqttBrokerHost, setCommunityMqttBrokerHost] = useState('mqtt-us-v1.letsmesh.net');
|
||||
const [communityMqttBrokerPort, setCommunityMqttBrokerPort] = useState('443');
|
||||
const [communityMqttEmail, setCommunityMqttEmail] = useState('');
|
||||
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -48,7 +49,8 @@ export function SettingsMqttSection({
|
||||
setMqttPublishRawPackets(appSettings.mqtt_publish_raw_packets ?? false);
|
||||
setCommunityMqttEnabled(appSettings.community_mqtt_enabled ?? false);
|
||||
setCommunityMqttIata(appSettings.community_mqtt_iata ?? '');
|
||||
setCommunityMqttBroker(appSettings.community_mqtt_broker ?? 'mqtt-us-v1.letsmesh.net');
|
||||
setCommunityMqttBrokerHost(appSettings.community_mqtt_broker_host ?? 'mqtt-us-v1.letsmesh.net');
|
||||
setCommunityMqttBrokerPort(String(appSettings.community_mqtt_broker_port ?? 443));
|
||||
setCommunityMqttEmail(appSettings.community_mqtt_email ?? '');
|
||||
}, [appSettings]);
|
||||
|
||||
@@ -69,7 +71,8 @@ export function SettingsMqttSection({
|
||||
mqtt_publish_raw_packets: mqttPublishRawPackets,
|
||||
community_mqtt_enabled: communityMqttEnabled,
|
||||
community_mqtt_iata: communityMqttIata,
|
||||
community_mqtt_broker: communityMqttBroker || 'mqtt-us-v1.letsmesh.net',
|
||||
community_mqtt_broker_host: communityMqttBrokerHost || 'mqtt-us-v1.letsmesh.net',
|
||||
community_mqtt_broker_port: parseInt(communityMqttBrokerPort, 10) || 443,
|
||||
community_mqtt_email: communityMqttEmail,
|
||||
};
|
||||
await onSaveAppSettings(update);
|
||||
@@ -113,49 +116,53 @@ export function SettingsMqttSection({
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-host">Broker Host</Label>
|
||||
<Input
|
||||
id="mqtt-host"
|
||||
type="text"
|
||||
placeholder="e.g. 192.168.1.100"
|
||||
value={mqttBrokerHost}
|
||||
onChange={(e) => setMqttBrokerHost(e.target.value)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-host">Broker Host</Label>
|
||||
<Input
|
||||
id="mqtt-host"
|
||||
type="text"
|
||||
placeholder="e.g. 192.168.1.100"
|
||||
value={mqttBrokerHost}
|
||||
onChange={(e) => setMqttBrokerHost(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-port">Broker Port</Label>
|
||||
<Input
|
||||
id="mqtt-port"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
value={mqttBrokerPort}
|
||||
onChange={(e) => setMqttBrokerPort(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-port">Broker Port</Label>
|
||||
<Input
|
||||
id="mqtt-port"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
value={mqttBrokerPort}
|
||||
onChange={(e) => setMqttBrokerPort(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-username">Username</Label>
|
||||
<Input
|
||||
id="mqtt-username"
|
||||
type="text"
|
||||
placeholder="Optional"
|
||||
value={mqttUsername}
|
||||
onChange={(e) => setMqttUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-username">Username</Label>
|
||||
<Input
|
||||
id="mqtt-username"
|
||||
type="text"
|
||||
placeholder="Optional"
|
||||
value={mqttUsername}
|
||||
onChange={(e) => setMqttUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-password">Password</Label>
|
||||
<Input
|
||||
id="mqtt-password"
|
||||
type="password"
|
||||
placeholder="Optional"
|
||||
value={mqttPassword}
|
||||
onChange={(e) => setMqttPassword(e.target.value)}
|
||||
/>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mqtt-password">Password</Label>
|
||||
<Input
|
||||
id="mqtt-password"
|
||||
type="password"
|
||||
placeholder="Optional"
|
||||
value={mqttPassword}
|
||||
onChange={(e) => setMqttPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
@@ -278,15 +285,29 @@ export function SettingsMqttSection({
|
||||
|
||||
{communityMqttEnabled && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="community-broker">Broker Address</Label>
|
||||
<Input
|
||||
id="community-broker"
|
||||
type="text"
|
||||
placeholder="mqtt-us-v1.letsmesh.net:443"
|
||||
value={communityMqttBroker}
|
||||
onChange={(e) => setCommunityMqttBroker(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">host or host:port (default port 443)</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="community-broker-host">Broker Host</Label>
|
||||
<Input
|
||||
id="community-broker-host"
|
||||
type="text"
|
||||
placeholder="mqtt-us-v1.letsmesh.net"
|
||||
value={communityMqttBrokerHost}
|
||||
onChange={(e) => setCommunityMqttBrokerHost(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="community-broker-port">Broker Port</Label>
|
||||
<Input
|
||||
id="community-broker-port"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
value={communityMqttBrokerPort}
|
||||
onChange={(e) => setCommunityMqttBrokerPort(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Label htmlFor="community-iata">Region Code (IATA)</Label>
|
||||
<Input
|
||||
id="community-iata"
|
||||
|
||||
@@ -63,7 +63,8 @@ const baseSettings: AppSettings = {
|
||||
mqtt_publish_raw_packets: false,
|
||||
community_mqtt_enabled: false,
|
||||
community_mqtt_iata: '',
|
||||
community_mqtt_broker: 'mqtt-us-v1.letsmesh.net',
|
||||
community_mqtt_broker_host: 'mqtt-us-v1.letsmesh.net',
|
||||
community_mqtt_broker_port: 443,
|
||||
community_mqtt_email: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -195,7 +195,8 @@ export interface AppSettings {
|
||||
mqtt_publish_raw_packets: boolean;
|
||||
community_mqtt_enabled: boolean;
|
||||
community_mqtt_iata: string;
|
||||
community_mqtt_broker: string;
|
||||
community_mqtt_broker_host: string;
|
||||
community_mqtt_broker_port: number;
|
||||
community_mqtt_email: string;
|
||||
}
|
||||
|
||||
@@ -216,7 +217,8 @@ export interface AppSettingsUpdate {
|
||||
mqtt_publish_raw_packets?: boolean;
|
||||
community_mqtt_enabled?: boolean;
|
||||
community_mqtt_iata?: string;
|
||||
community_mqtt_broker?: string;
|
||||
community_mqtt_broker_host?: string;
|
||||
community_mqtt_broker_port?: number;
|
||||
community_mqtt_email?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,12 @@ import pytest
|
||||
from app.community_mqtt import (
|
||||
_CLIENT_ID,
|
||||
_DEFAULT_BROKER,
|
||||
_DEFAULT_PORT,
|
||||
CommunityMqttPublisher,
|
||||
_base64url_encode,
|
||||
_calculate_packet_hash,
|
||||
_ed25519_sign_expanded,
|
||||
_format_raw_packet,
|
||||
_generate_jwt_token,
|
||||
_parse_broker_address,
|
||||
community_mqtt_broadcast,
|
||||
)
|
||||
from app.models import AppSettings
|
||||
@@ -423,33 +421,6 @@ class TestCommunityMqttBroadcast:
|
||||
mock_task.assert_not_called()
|
||||
|
||||
|
||||
class TestParseBrokerAddress:
|
||||
def test_hostname_only_uses_default_port(self):
|
||||
host, port = _parse_broker_address("mqtt-us-v1.letsmesh.net")
|
||||
assert host == "mqtt-us-v1.letsmesh.net"
|
||||
assert port == _DEFAULT_PORT
|
||||
|
||||
def test_hostname_with_port(self):
|
||||
host, port = _parse_broker_address("mqtt-us-v1.letsmesh.net:8883")
|
||||
assert host == "mqtt-us-v1.letsmesh.net"
|
||||
assert port == 8883
|
||||
|
||||
def test_hostname_with_port_443(self):
|
||||
host, port = _parse_broker_address("broker.example.com:443")
|
||||
assert host == "broker.example.com"
|
||||
assert port == 443
|
||||
|
||||
def test_invalid_port_uses_default(self):
|
||||
host, port = _parse_broker_address("broker.example.com:abc")
|
||||
assert host == "broker.example.com:abc"
|
||||
assert port == _DEFAULT_PORT
|
||||
|
||||
def test_empty_string(self):
|
||||
host, port = _parse_broker_address("")
|
||||
assert host == ""
|
||||
assert port == _DEFAULT_PORT
|
||||
|
||||
|
||||
class TestPublishFailureSetsDisconnected:
|
||||
@pytest.mark.asyncio
|
||||
async def test_publish_error_sets_connected_false(self):
|
||||
|
||||
@@ -929,13 +929,15 @@ class TestMigration032:
|
||||
# Verify all columns exist with correct defaults
|
||||
cursor = await conn.execute(
|
||||
"""SELECT community_mqtt_enabled, community_mqtt_iata,
|
||||
community_mqtt_broker, community_mqtt_email
|
||||
community_mqtt_broker_host, community_mqtt_broker_port,
|
||||
community_mqtt_email
|
||||
FROM app_settings WHERE id = 1"""
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
assert row["community_mqtt_enabled"] == 0
|
||||
assert row["community_mqtt_iata"] == ""
|
||||
assert row["community_mqtt_broker"] == "mqtt-us-v1.letsmesh.net"
|
||||
assert row["community_mqtt_broker_host"] == "mqtt-us-v1.letsmesh.net"
|
||||
assert row["community_mqtt_broker_port"] == 443
|
||||
assert row["community_mqtt_email"] == ""
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
@@ -504,7 +504,8 @@ class TestAppSettingsRepository:
|
||||
"mqtt_publish_raw_packets": 0,
|
||||
"community_mqtt_enabled": 0,
|
||||
"community_mqtt_iata": "",
|
||||
"community_mqtt_broker": "mqtt-us-v1.letsmesh.net",
|
||||
"community_mqtt_broker_host": "mqtt-us-v1.letsmesh.net",
|
||||
"community_mqtt_broker_port": 443,
|
||||
"community_mqtt_email": "",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -126,21 +126,24 @@ class TestUpdateSettings:
|
||||
AppSettingsUpdate(
|
||||
community_mqtt_enabled=True,
|
||||
community_mqtt_iata="DEN",
|
||||
community_mqtt_broker="custom-broker.example.com",
|
||||
community_mqtt_broker_host="custom-broker.example.com",
|
||||
community_mqtt_broker_port=8883,
|
||||
community_mqtt_email="test@example.com",
|
||||
)
|
||||
)
|
||||
|
||||
assert result.community_mqtt_enabled is True
|
||||
assert result.community_mqtt_iata == "DEN"
|
||||
assert result.community_mqtt_broker == "custom-broker.example.com"
|
||||
assert result.community_mqtt_broker_host == "custom-broker.example.com"
|
||||
assert result.community_mqtt_broker_port == 8883
|
||||
assert result.community_mqtt_email == "test@example.com"
|
||||
|
||||
# Verify persistence
|
||||
fresh = await AppSettingsRepository.get()
|
||||
assert fresh.community_mqtt_enabled is True
|
||||
assert fresh.community_mqtt_iata == "DEN"
|
||||
assert fresh.community_mqtt_broker == "custom-broker.example.com"
|
||||
assert fresh.community_mqtt_broker_host == "custom-broker.example.com"
|
||||
assert fresh.community_mqtt_broker_port == 8883
|
||||
assert fresh.community_mqtt_email == "test@example.com"
|
||||
|
||||
# Verify restart was called
|
||||
|
||||
Reference in New Issue
Block a user