Some checks failed
Security / Security check (push) Failing after 2s
Review follow-ups on the auth-status work: - Both daemon-start paths classified an auth-rejected hub as "daemon dead" via healthy() and showed a start-failure dialog while the shell banner above correctly blamed the token. They now share daemonAnswers(): only an unreachable probe counts as down. - An auth-rejected poll now re-reads ~/.chain/hub-auth-token and reconnects when the file changed, so a token fixed outside Studio (CLI, editor) heals the connection without a restart — previously the client kept the stale in-memory token forever and the banner's own advice could not work. - An endpoint switch resets the failure streak, so a stale in-flight probe can no longer let the unreachable banner blame the new endpoint for the old one's misses. - The auth-policy panel re-queries when the hub token is saved or cleared in the panel above (reloadTick), instead of keeping a stale admin-denied hint; it also renders the hub's new reload_required flag as a pending-reload warning (DE+EN). - today-pipeline.md still documented ~/.fai/today after the rename; the FAB theme comment now states the both-themes intent. flutter analyze clean; 71 tests green including four new ones. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
100 lines
3.8 KiB
Dart
100 lines
3.8 KiB
Dart
// The shell's sustained-failure banner must name the actual
|
|
// problem: a hub that rejects the token (UNAUTHENTICATED /
|
|
// PERMISSION_DENIED) is UP — telling the operator "can't reach
|
|
// the hub" sends them to fix the endpoint when the token is what
|
|
// needs attention. The probe result is faked through
|
|
// StudioShellState.debugProbeOverride; the banner appears after
|
|
// three consecutive failed polls (5 s apart), so each test pumps
|
|
// the poll timer forward instead of waiting.
|
|
|
|
import 'package:chain_client_sdk/chain_client_sdk.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:chain_studio/data/hub.dart';
|
|
import 'package:chain_studio/main.dart';
|
|
|
|
Future<void> _pumpShell(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const StudioApp(
|
|
initialThemeMode: ThemeModeValue.system,
|
|
initialLocale: Locale('en'),
|
|
),
|
|
);
|
|
// pump (not pumpAndSettle): the app has long-lived timers that
|
|
// never settle. One frame lays the shell out, then three poll
|
|
// ticks cross the banner threshold.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
for (var i = 0; i < 3; i++) {
|
|
await tester.pump(const Duration(seconds: 5));
|
|
await tester.pump();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
tearDown(() {
|
|
StudioShellState.debugProbeOverride = null;
|
|
StudioShellState.debugReloadTokenOverride = null;
|
|
});
|
|
|
|
testWidgets('sustained auth rejection points at the token, not the wire',
|
|
(tester) async {
|
|
StudioShellState.debugProbeOverride =
|
|
() async => HubProbeResult.authRejected;
|
|
await _pumpShell(tester);
|
|
|
|
expect(find.textContaining('rejected the sign-in'), findsOneWidget);
|
|
expect(find.textContaining("Can't reach"), findsNothing);
|
|
|
|
// Token fixed (probe turns serving): the banner must clear.
|
|
StudioShellState.debugProbeOverride = () async => HubProbeResult.serving;
|
|
await tester.pump(const Duration(seconds: 5));
|
|
await tester.pump();
|
|
expect(find.textContaining('rejected the sign-in'), findsNothing);
|
|
});
|
|
|
|
testWidgets('sustained connection failure keeps the unreachable wording',
|
|
(tester) async {
|
|
StudioShellState.debugProbeOverride =
|
|
() async => HubProbeResult.unreachable;
|
|
await _pumpShell(tester);
|
|
|
|
expect(find.textContaining("Can't reach"), findsOneWidget);
|
|
expect(find.textContaining('rejected the sign-in'), findsNothing);
|
|
});
|
|
|
|
testWidgets('auth-rejected poll re-reads the token file and re-probes',
|
|
(tester) async {
|
|
// First probe(s) rejected; after the token file "changes" the
|
|
// immediate re-probe serves — the operator fixed the token via
|
|
// CLI/editor and Studio healed without waiting a poll cycle.
|
|
var probes = 0;
|
|
var reloads = 0;
|
|
StudioShellState.debugProbeOverride = () async {
|
|
probes++;
|
|
return reloads > 0 ? HubProbeResult.serving : HubProbeResult.authRejected;
|
|
};
|
|
StudioShellState.debugReloadTokenOverride = () async {
|
|
reloads++;
|
|
return true; // token file content changed → reconnected
|
|
};
|
|
await _pumpShell(tester);
|
|
|
|
expect(reloads, greaterThanOrEqualTo(1));
|
|
// The self-heal re-probe ran within the same tick (probes >
|
|
// reloads means at least one immediate retry happened) and the
|
|
// banner never surfaces.
|
|
expect(probes, greaterThan(reloads));
|
|
expect(find.textContaining('rejected the sign-in'), findsNothing);
|
|
expect(find.textContaining("Can't reach"), findsNothing);
|
|
});
|
|
|
|
testWidgets('daemonAnswers treats auth-rejected as alive', (tester) async {
|
|
StudioShellState.debugProbeOverride =
|
|
() async => HubProbeResult.authRejected;
|
|
expect(await StudioShellState.daemonAnswers(), isTrue);
|
|
|
|
StudioShellState.debugProbeOverride =
|
|
() async => HubProbeResult.unreachable;
|
|
expect(await StudioShellState.daemonAnswers(), isFalse);
|
|
});
|
|
}
|