From 6cd707218dcbcaea0b45a61fca0b302c143c6c17 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 23 Jun 2026 01:39:22 -0400 Subject: [PATCH] fix(#80): only URL/port gate a broker enable; firmware owns the rest On-hardware feedback: JWT owner defaults to the device pubkey and IATA override is optional by definition, so gating a save on them is wrong -- they have under-the-hood firmware defaults. BrokerConfig.enableError now checks only the structural fields with no default: URL present, port in range. The JWT field checks are gone; the firmware enforces JWT completeness with its defaults, and a genuinely-bad enable surfaces the firmware's reason via the failure messaging added earlier. The editor's pre-save check and the list's quick Enable both follow. Tests updated to the new contract (a JWT broker with blank owner/audience now enables; a structurally-complete quick Enable confirms). Full suite green. Co-Authored-By: Claude Opus 4.8 --- lib/models/observer_config.dart | 6 ++---- lib/screens/settings/broker_editor_screen.dart | 6 ++---- test/screens/broker_editor_screen_test.dart | 17 +++++++++++++---- test/screens/mqtt_brokers_screen_test.dart | 16 ++++------------ 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/lib/models/observer_config.dart b/lib/models/observer_config.dart index 76814d6..2e7f493 100644 --- a/lib/models/observer_config.dart +++ b/lib/models/observer_config.dart @@ -150,10 +150,8 @@ class BrokerConfig { String? get enableError { if (url.isEmpty) return 'URL is required'; if (port < 1 || port > 65535) return 'Port must be between 1 and 65535'; - if (authType == BrokerAuthType.jwt && - (jwtAudience.isEmpty || jwtOwner.isEmpty)) { - return 'JWT auth needs an audience and an owner'; - } + // JWT owner (device pubkey), IATA override, etc. are firmware-defaulted, so + // the client does NOT gate them — the firmware enforces with its defaults. return null; } diff --git a/lib/screens/settings/broker_editor_screen.dart b/lib/screens/settings/broker_editor_screen.dart index 8952168..3bfb170 100644 --- a/lib/screens/settings/broker_editor_screen.dart +++ b/lib/screens/settings/broker_editor_screen.dart @@ -113,14 +113,12 @@ class _BrokerEditorScreenState extends State { _changedFields().isNotEmpty || _enabled != _baseline.enabled; /// Pre-enable validation, shared with the list's quick Enable via - /// [BrokerConfig.enableError]. Builds a tentative config from the form. + /// [BrokerConfig.enableError]. Only structural fields (URL, port) gate a save + /// — the firmware enforces the rest via its own defaults. String? _validate() => BrokerConfig( slot: _baseline.slot, url: _url.text.trim(), port: int.tryParse(_port.text.trim()) ?? -1, - authType: _authType, - jwtAudience: _jwtAudience.text.trim(), - jwtOwner: _jwtOwner.text.trim(), ).enableError; void _snack(String msg, {bool isError = false}) { diff --git a/test/screens/broker_editor_screen_test.dart b/test/screens/broker_editor_screen_test.dart index 361a15e..61f569e 100644 --- a/test/screens/broker_editor_screen_test.dart +++ b/test/screens/broker_editor_screen_test.dart @@ -203,7 +203,9 @@ void main() { await _drainSnack(tester); }); - testWidgets('enabling with incomplete JWT is blocked', (tester) async { + testWidgets('a JWT broker with blank owner/audience still enables', ( + tester, + ) async { final fake = _FakeSvc(); await _open( tester, @@ -212,7 +214,8 @@ void main() { slot: 2, url: 'h', port: 1883, - authType: BrokerAuthType.jwt, // disabled, blank audience/owner + authType: + BrokerAuthType.jwt, // blank owner/audience -> firmware default ), ); @@ -221,8 +224,14 @@ void main() { await tester.tap(find.byTooltip('Save')); await tester.pumpAndSettle(); - expect(fake.saveCalls, isEmpty); - expect(find.textContaining('needs an audience'), findsOneWidget); + expect( + fake.saveCalls, + hasLength(1), + reason: + 'the client no longer gates JWT fields — the firmware enforces them ' + 'with its defaults (owner -> device pubkey)', + ); + expect(fake.saveCalls.single.enable, isTrue); await _drainSnack(tester); }); } diff --git a/test/screens/mqtt_brokers_screen_test.dart b/test/screens/mqtt_brokers_screen_test.dart index 7e645f5..9be7482 100644 --- a/test/screens/mqtt_brokers_screen_test.dart +++ b/test/screens/mqtt_brokers_screen_test.dart @@ -102,12 +102,8 @@ void main() { expect(fake.clearCalls, contains(2)); }); - testWidgets('quick Enable refuses an incomplete broker with a reason', ( - tester, - ) async { - final fake = _FakeSvc(const [ - BrokerConfig(slot: 2, url: 'a', port: 1883, authType: BrokerAuthType.jwt), - ]); + testWidgets('a successful quick Enable confirms', (tester) async { + final fake = _FakeSvc(const [BrokerConfig(slot: 2, url: 'a', port: 1883)]); await _pump(tester, fake); await tester.longPress(find.text('[2] a')); @@ -115,12 +111,8 @@ void main() { await tester.tap(find.text('Enable')); await tester.pumpAndSettle(); - expect( - fake.setCalls, - isEmpty, - reason: 'an incomplete broker must not be enabled', - ); - expect(find.textContaining("Can't enable"), findsOneWidget); + expect(fake.setCalls, contains('2.enabled=1')); + expect(find.textContaining('enabled'), findsOneWidget); await tester.pump(const Duration(seconds: 5)); });