feat(#89): verify broker enable/disable actually applied before confirming
A firmware build can ACK a broker enable/disable as success without applying it (HV4; meshcore-firmware#179). The quick toggle and the editor save previously trusted the ACK and showed a success snackbar, so an ack-but-no-op looked like it worked. Both paths now SET, wait a short settle, re-read the slot, and report what the device ACTUALLY did: - applied -> confirm - notApplied -> warn "possible firmware issue"; the editor stays and re-seeds to the device's true state instead of popping a false success - unverified -> re-read failed (e.g. an enable that rebooted the device) Every device is treated identically: the UI reflects the re-read, never assuming a change took that the device didn't confirm. - BrokerConfig.classifyApply pure classifier + BrokerApplyOutcome (unit-tested) - ObserverConfigService.applySettleDelay - wired into mqtt_brokers_screen quick toggle + broker_editor_screen save - widget test for the ACK-but-not-applied path Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>pull/99/head
parent
3015b41f55
commit
4d7921d2f7
@ -0,0 +1,61 @@
|
|||||||
|
// Honest-apply classifier (#89). A firmware build can ACK a broker
|
||||||
|
// enable/disable as success but not apply it (HV4 desync,
|
||||||
|
// meshcore-firmware#179). After a toggle/save the UI re-reads the slot and
|
||||||
|
// compares to what it asked for; this classifier turns (intended, re-read) into
|
||||||
|
// the outcome the UI reports. It treats every device identically — it only ever
|
||||||
|
// reflects the re-read, never assuming the change took.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_open/models/observer_config.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('BrokerConfig.classifyApply', () {
|
||||||
|
test('enable the device confirms -> applied', () {
|
||||||
|
expect(
|
||||||
|
BrokerConfig.classifyApply(intendedEnabled: true, actualEnabled: true),
|
||||||
|
BrokerApplyOutcome.applied,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disable the device confirms -> applied', () {
|
||||||
|
expect(
|
||||||
|
BrokerConfig.classifyApply(
|
||||||
|
intendedEnabled: false,
|
||||||
|
actualEnabled: false,
|
||||||
|
),
|
||||||
|
BrokerApplyOutcome.applied,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'disable ACKed but re-read still enabled -> notApplied (the HV4 bug)',
|
||||||
|
() {
|
||||||
|
expect(
|
||||||
|
BrokerConfig.classifyApply(
|
||||||
|
intendedEnabled: false,
|
||||||
|
actualEnabled: true,
|
||||||
|
),
|
||||||
|
BrokerApplyOutcome.notApplied,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('enable ACKed but re-read still disabled -> notApplied', () {
|
||||||
|
expect(
|
||||||
|
BrokerConfig.classifyApply(intendedEnabled: true, actualEnabled: false),
|
||||||
|
BrokerApplyOutcome.notApplied,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('re-read failed (null) -> unverified, whatever the intent', () {
|
||||||
|
expect(
|
||||||
|
BrokerConfig.classifyApply(intendedEnabled: true, actualEnabled: null),
|
||||||
|
BrokerApplyOutcome.unverified,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
BrokerConfig.classifyApply(intendedEnabled: false, actualEnabled: null),
|
||||||
|
BrokerApplyOutcome.unverified,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in new issue