feat(#93): render broker runtime-state + resolved-default placeholders
Consume the additive OCFG_BROKERS dump fields from firmware #172/#173 (backward-compatible — absent fields read as today's enabled/disabled): - BrokerRuntimeState (`state`) + BrokerLastError (`last_error`) + a pure BrokerConfig.status mapping -> Connected / Connecting / Failed (reason) / Held (no clock|low heap) / Enabled / Disabled; held shown neutral, not error. - Brokers list shows the real status line + colour instead of just "enabled". - Editor shows jwt_owner / iata_override resolved defaults (`jwt_owner_resolved`, `iata_resolved`) as greyed placeholders when the raw key is blank; only writes the raw key when the user enters a value (blank stays the source of truth). Sourced from the pool dump (getBrokers); getBroker's per-field GET list is unchanged pending the firmware GET-ability answer on #173. Unit-tested mapping, parsing, and backward-compat (9 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>pull/99/head
parent
4d7921d2f7
commit
37d2ca9fb1
@ -0,0 +1,105 @@
|
|||||||
|
// Broker runtime-status mapping (#93, firmware #172/#173). Maps the additive
|
||||||
|
// `state` + `last_error` dump fields to the human status the UI shows, and
|
||||||
|
// parses the resolved-default placeholders. Absent fields (older firmware) must
|
||||||
|
// read as today's plain enabled/disabled.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_open/models/observer_config.dart';
|
||||||
|
|
||||||
|
BrokerConfig _broker({
|
||||||
|
bool enabled = true,
|
||||||
|
BrokerRuntimeState state = BrokerRuntimeState.unknown,
|
||||||
|
BrokerLastError lastError = BrokerLastError.none,
|
||||||
|
}) => BrokerConfig(
|
||||||
|
slot: 0,
|
||||||
|
url: 'mqtt.example',
|
||||||
|
port: 1883,
|
||||||
|
enabled: enabled,
|
||||||
|
runtimeState: state,
|
||||||
|
lastError: lastError,
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('BrokerConfig.status', () {
|
||||||
|
test('disabled overrides any runtime state', () {
|
||||||
|
final s = _broker(enabled: false, state: BrokerRuntimeState.up).status;
|
||||||
|
expect(s.label, 'Disabled');
|
||||||
|
expect(s.kind, BrokerStatusKind.disabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('up -> Connected', () {
|
||||||
|
final s = _broker(state: BrokerRuntimeState.up).status;
|
||||||
|
expect(s.label, 'Connected');
|
||||||
|
expect(s.kind, BrokerStatusKind.connected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connecting -> Connecting', () {
|
||||||
|
expect(
|
||||||
|
_broker(state: BrokerRuntimeState.connecting).status.kind,
|
||||||
|
BrokerStatusKind.connecting,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('backoff qualifies the failure with last_error', () {
|
||||||
|
final s = _broker(
|
||||||
|
state: BrokerRuntimeState.backoff,
|
||||||
|
lastError: BrokerLastError.auth,
|
||||||
|
).status;
|
||||||
|
expect(s.label, 'Failed (auth)');
|
||||||
|
expect(s.kind, BrokerStatusKind.failed);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('backoff with no error class -> plain Failed', () {
|
||||||
|
final s = _broker(state: BrokerRuntimeState.backoff).status;
|
||||||
|
expect(s.label, 'Failed');
|
||||||
|
expect(s.kind, BrokerStatusKind.failed);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('held states are neutral, not failures', () {
|
||||||
|
expect(
|
||||||
|
_broker(state: BrokerRuntimeState.heldNoClock).status.kind,
|
||||||
|
BrokerStatusKind.held,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
_broker(state: BrokerRuntimeState.heldNoHeap).status.label,
|
||||||
|
'Held — low heap',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unknown state (older firmware) reads as plain Enabled', () {
|
||||||
|
final s = _broker(state: BrokerRuntimeState.unknown).status;
|
||||||
|
expect(s.label, 'Enabled');
|
||||||
|
expect(s.kind, BrokerStatusKind.idle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('BrokerConfig.fromWireFields runtime + resolved fields', () {
|
||||||
|
test('parses state + last_error + resolved defaults', () {
|
||||||
|
final b = BrokerConfig.fromWireFields(2, {
|
||||||
|
'url': 'mqtt.example',
|
||||||
|
'enabled': '1',
|
||||||
|
'state': 'backoff',
|
||||||
|
'last_error': 'tls',
|
||||||
|
'jwt_owner_resolved': 'ABCDEF',
|
||||||
|
'iata_resolved': 'HAO',
|
||||||
|
});
|
||||||
|
expect(b.runtimeState, BrokerRuntimeState.backoff);
|
||||||
|
expect(b.lastError, BrokerLastError.tls);
|
||||||
|
expect(b.jwtOwnerResolved, 'ABCDEF');
|
||||||
|
expect(b.iataResolved, 'HAO');
|
||||||
|
expect(b.status.label, 'Failed (tls)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('absent runtime fields default to unknown/none (backward compat)', () {
|
||||||
|
final b = BrokerConfig.fromWireFields(0, {
|
||||||
|
'url': 'mqtt.example',
|
||||||
|
'enabled': '1',
|
||||||
|
});
|
||||||
|
expect(b.runtimeState, BrokerRuntimeState.unknown);
|
||||||
|
expect(b.lastError, BrokerLastError.none);
|
||||||
|
expect(b.jwtOwnerResolved, '');
|
||||||
|
expect(b.iataResolved, '');
|
||||||
|
expect(b.status.label, 'Enabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in new issue