mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fix: GUI satpass enabled toggle now persists through adapter-config API
The Tracking panel toggle for satpass.enabled was silently dropped on save because SatpassConfig lacked the enabled field. The toggle wrote to a phantom env.satpass.enabled (not in EnvConfig) which the environmental config endpoint ignored. Handlers read enabled from the adapter_config SQLite table, which was never updated. Changes: - Add enabled:boolean to SatpassConfig interface + initial state - Convert GET /api/adapter-config/satpass array response to keyed dict so load actually reads saved values (fixes pre-existing load bug) - Wire enabled into the save path via saveAdapterConfig PUT - Override AdapterPanel enabled/onEnabled for satpass to use satpassConfig instead of the phantom env field - Add test_bool_roundtrip.py: 6 tests proving PUT true/false round- trips through DB (value_json) and accessor (Python bool), plus second+third adapter boolean round-trips (wfigs, fires) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
116e66369e
commit
199929ff1c
4 changed files with 227 additions and 71 deletions
|
|
@ -74,6 +74,7 @@ interface AvalancheConfig {
|
|||
|
||||
// Satpass adapter config shape
|
||||
interface SatpassConfig {
|
||||
enabled: boolean
|
||||
observers: string[]
|
||||
min_elevation: number
|
||||
norad_ids: string[]
|
||||
|
|
@ -310,6 +311,7 @@ export default function Environment() {
|
|||
})
|
||||
const [swpcOriginal, setSwpcOriginal] = useState<string>("")
|
||||
const [satpassConfig, setSatpassConfig] = useState<SatpassConfig>({
|
||||
enabled: false,
|
||||
observers: [],
|
||||
min_elevation: 30,
|
||||
norad_ids: [],
|
||||
|
|
@ -449,8 +451,11 @@ export default function Environment() {
|
|||
try {
|
||||
const satpassRes = await fetch("/api/adapter-config/satpass")
|
||||
if (satpassRes.ok) {
|
||||
const satpassData = await satpassRes.json()
|
||||
const satpassArr = await satpassRes.json()
|
||||
const satpassData: Record<string, any> = {}
|
||||
for (const r of satpassArr) satpassData[r.key] = r
|
||||
const cfg: SatpassConfig = {
|
||||
enabled: satpassData.enabled?.value ?? false,
|
||||
observers: satpassData.observers?.value ?? [],
|
||||
min_elevation: satpassData.min_elevation?.value ?? 30,
|
||||
norad_ids: satpassData.norad_ids?.value ?? [],
|
||||
|
|
@ -642,6 +647,9 @@ const save = async () => {
|
|||
// Save satpass adapter config changes
|
||||
if (hasSatpassChanges) {
|
||||
const orig = JSON.parse(satpassOriginal) as SatpassConfig
|
||||
if (satpassConfig.enabled !== orig.enabled) {
|
||||
await saveAdapterConfig("satpass", "enabled", satpassConfig.enabled)
|
||||
}
|
||||
if (JSON.stringify(satpassConfig.observers) !== JSON.stringify(orig.observers)) {
|
||||
await saveAdapterConfig("satpass", "observers", satpassConfig.observers)
|
||||
}
|
||||
|
|
@ -1209,8 +1217,8 @@ const save = async () => {
|
|||
<AdapterPanel
|
||||
title={META[activeAdapter].label}
|
||||
subtitle={META[activeAdapter].subtitle}
|
||||
enabled={a[activeAdapter]?.enabled ?? false}
|
||||
onEnabled={(v) => setAdapterField(activeAdapter, { enabled: v })}
|
||||
enabled={activeAdapter === 'satpass' ? satpassConfig.enabled : (a[activeAdapter]?.enabled ?? false)}
|
||||
onEnabled={(v) => activeAdapter === 'satpass' ? setSatpassConfig({ ...satpassConfig, enabled: v }) : setAdapterField(activeAdapter, { enabled: v })}
|
||||
feedSource={a[activeAdapter]?.feed_source ?? 'native'}
|
||||
onFeedSource={(v) => setAdapterField(activeAdapter, { feed_source: v })}
|
||||
hasCentral={META[activeAdapter].hasCentral}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,7 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script type="module" crossorigin src="/assets/index-D7cLYkH6.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DPN58SF4.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CB06j1ej.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
148
tests/test_bool_roundtrip.py
Normal file
148
tests/test_bool_roundtrip.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
"""Boolean round-trip tests for adapter_config enabled toggle.
|
||||
|
||||
Proves:
|
||||
1. PUT satpass.enabled=true -> value_json='true' in DB -> accessor reads Python True
|
||||
2. PUT satpass.enabled=false -> value_json='false' in DB -> accessor reads Python False
|
||||
3. Same round-trip on a second adapter (wfigs.broadcast_on_acres) -> not satpass-special
|
||||
4. PUT rejects non-bool values (string 'true', int 1)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from meshai.adapter_config import adapter_config, invalidate_cache
|
||||
from meshai.dashboard.api.adapter_config_routes import router
|
||||
from meshai.persistence import get_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api")
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
# -- satpass.enabled boolean round-trip --
|
||||
|
||||
def test_satpass_enabled_true_roundtrip(client):
|
||||
"""PUT enabled=true -> DB value_json='true' -> accessor returns Python True."""
|
||||
# Default is false
|
||||
assert adapter_config.satpass.enabled is False
|
||||
|
||||
# PUT true
|
||||
r = client.put(
|
||||
"/api/adapter-config/satpass/enabled",
|
||||
json={"value": True},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["value"] is True
|
||||
|
||||
# DB has value_json='true' (the JSON literal, not Python repr)
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT value_json FROM adapter_config WHERE adapter='satpass' AND key='enabled'"
|
||||
).fetchone()
|
||||
assert row is not None
|
||||
assert row["value_json"] == "true", (
|
||||
f"expected value_json='true', got {row['value_json']!r}"
|
||||
)
|
||||
|
||||
# Accessor reads Python True (not string, not int)
|
||||
val = adapter_config.satpass.enabled
|
||||
assert val is True
|
||||
assert type(val) is bool
|
||||
|
||||
|
||||
def test_satpass_enabled_false_roundtrip(client):
|
||||
"""PUT enabled=false after enabling -> DB value_json='false' -> accessor False."""
|
||||
# Enable first
|
||||
client.put("/api/adapter-config/satpass/enabled", json={"value": True})
|
||||
assert adapter_config.satpass.enabled is True
|
||||
|
||||
# Disable
|
||||
r = client.put(
|
||||
"/api/adapter-config/satpass/enabled",
|
||||
json={"value": False},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["value"] is False
|
||||
|
||||
# DB
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT value_json FROM adapter_config WHERE adapter='satpass' AND key='enabled'"
|
||||
).fetchone()
|
||||
assert row["value_json"] == "false"
|
||||
|
||||
# Accessor
|
||||
assert adapter_config.satpass.enabled is False
|
||||
|
||||
|
||||
def test_satpass_enabled_rejects_string_true(client):
|
||||
"""String 'true' must be rejected -- only Python bool accepted."""
|
||||
r = client.put(
|
||||
"/api/adapter-config/satpass/enabled",
|
||||
json={"value": "true"},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_satpass_enabled_rejects_int_one(client):
|
||||
"""Integer 1 must be rejected -- only Python bool accepted."""
|
||||
r = client.put(
|
||||
"/api/adapter-config/satpass/enabled",
|
||||
json={"value": 1},
|
||||
)
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
# -- Second adapter boolean round-trip (not satpass-special) --
|
||||
|
||||
def test_wfigs_broadcast_on_acres_bool_roundtrip(client):
|
||||
"""Same round-trip on wfigs.broadcast_on_acres proves bool handling is generic."""
|
||||
# Read default
|
||||
default_val = adapter_config.wfigs.broadcast_on_acres
|
||||
|
||||
# Flip it
|
||||
new_val = not default_val
|
||||
r = client.put(
|
||||
"/api/adapter-config/wfigs/broadcast_on_acres",
|
||||
json={"value": new_val},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["value"] is new_val
|
||||
|
||||
# DB has correct JSON literal
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT value_json FROM adapter_config WHERE adapter='wfigs' AND key='broadcast_on_acres'"
|
||||
).fetchone()
|
||||
assert row["value_json"] == json.dumps(new_val)
|
||||
|
||||
# Accessor
|
||||
val = adapter_config.wfigs.broadcast_on_acres
|
||||
assert val is new_val
|
||||
assert type(val) is bool
|
||||
|
||||
|
||||
def test_fires_digest_enabled_bool_roundtrip(client):
|
||||
"""Third adapter (fires.digest_enabled) -- additional proof of generic handling."""
|
||||
# Read default
|
||||
default_val = adapter_config.fires.digest_enabled
|
||||
|
||||
# Flip
|
||||
new_val = not default_val
|
||||
r = client.put(
|
||||
"/api/adapter-config/fires/digest_enabled",
|
||||
json={"value": new_val},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["value"] is new_val
|
||||
|
||||
# Accessor returns the correct bool
|
||||
assert adapter_config.fires.digest_enabled is new_val
|
||||
assert type(adapter_config.fires.digest_enabled) is bool
|
||||
Loading…
Add table
Add a link
Reference in a new issue