import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:iop_client/control_plane_status_client.dart'; import 'package:iop_client/main.dart'; import 'support/client_test_harness.dart'; void main() { _registerPanelRenderingScenario(); _registerCommandErrorScenario(); _registerCommandGatingScenario(); _registerPendingEmptyHistoryScenario(); _registerOperationsFetchStateScenario(); } void _registerPanelRenderingScenario() { testWidgets( 'Client App opens Operations & Domain Agents panel and verifies agents, operations history, and command triggering', (WidgetTester tester) async { final fakeClient = FakeClientWireClient(shouldSuccess: true); final fakeStatusRepo = FakeControlPlaneStatusRepository(); await tester.pumpWidget( IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byTooltip('Runtime')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); _verifyPanelHeaderAndSections(tester); _verifyDomainAgentsSummary(tester); _verifyActiveCommandPresence(tester); _verifyActiveCommandAbsence(tester); _verifyOperationsHistoryItem(tester); _verifyTriggerSyncAbsent(tester); _verifySystemGatedButtons(tester); await _verifyHealthCheckSuccess(tester, fakeStatusRepo); fakeStatusRepo.edgeBOperationsCompleter = Completer(); await tester.tap(find.byType(DropdownButton)); await tester.pumpAndSettle(); await tester.tap(find.text('Edge Beta').last); await tester.pump(); _verifyEdgeSwitchNotLoaded(tester); fakeStatusRepo.edgeBOperationsCompleter!.complete( EdgeOperationsResponseView( edgeId: 'edge-b', operations: [ EdgeCommandRecordView( edgeId: 'edge-b', commandId: 'cmd-beta-active', operation: 'build-deploy.deploy', targetSelector: '', status: 'success', summary: 'Deploy queued for edge beta', error: '', ), ], ), ); await tester.pumpAndSettle(); _verifyEdgeBetaAgents(tester); _verifyEdgeBetaActiveCommand(tester); _verifyEdgeBetaOperationsHistory(tester); _verifyEdgeBetaActiveCommandAbsence(tester); _verifyEdgeBetaOperationsAbsence(tester); await tester.tap(find.text('Health Check')); await tester.pumpAndSettle(); expect(fakeStatusRepo.lastCommandEdgeId, equals('edge-b')); }, ); } void _registerCommandErrorScenario() { testWidgets( 'Client App handles unsupported or error command responses and shows error banner', (WidgetTester tester) async { final fakeClient = FakeClientWireClient(shouldSuccess: true); final fakeStatusRepo = FakeControlPlaneStatusRepository(); fakeStatusRepo.nextCommandStatus = 'unsupported'; fakeStatusRepo.nextCommandError = 'Operation not supported by Edge'; fakeStatusRepo.nextCommandSummary = 'Error summary'; await tester.pumpWidget( IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byTooltip('Runtime')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.text('Agent Status')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.text('Get Agent Status'), findsOneWidget); await tester.enterText(find.byType(TextField), 'test-node'); await tester.tap(find.text('Send')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect( find.textContaining( 'Failed: unsupported - Operation not supported by Edge', ), findsOneWidget, ); }, ); } void _registerCommandGatingScenario() { testWidgets( 'Client App gates agent.status and agent.command without required inputs', (WidgetTester tester) async { final fakeClient = FakeClientWireClient(shouldSuccess: true); final fakeStatusRepo = FakeControlPlaneStatusRepository(); await tester.pumpWidget( IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byTooltip('Runtime')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await _verifyAgentStatusGating(tester, fakeStatusRepo); await _verifyAgentCommandGating(tester, fakeStatusRepo); }, ); } void _registerPendingEmptyHistoryScenario() { testWidgets( 'RuntimePanel keeps loaded empty history visible while a command is pending', (WidgetTester tester) async { final fakeClient = FakeClientWireClient(shouldSuccess: true); final fakeStatusRepo = FakeControlPlaneStatusRepository(); fakeStatusRepo.emptyOperationsEdgeId = 'edge-a'; await tester.pumpWidget( IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byTooltip('Runtime')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.text('No operation executions recorded.'), findsOneWidget); fakeStatusRepo.commandResponseCompleter = Completer(); await tester.tap(find.text('Health Check')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.text('No operation executions recorded.'), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsNothing); expect(fakeStatusRepo.lastCommandOperation, equals('health.check')); expect(fakeStatusRepo.lastCommandEdgeId, equals('edge-a')); fakeStatusRepo.commandResponseCompleter!.complete( EdgeCommandResponseView( requestId: 'req-111', commandId: 'cmd-pending-test', edgeId: 'edge-a', status: 'accepted', summary: 'Command accepted by control plane', error: '', ), ); await tester.pumpAndSettle(); }, ); } void _registerOperationsFetchStateScenario() { testWidgets( 'RuntimePanel renders operations empty and fetch error states', (WidgetTester tester) async { final fakeClient = FakeClientWireClient(shouldSuccess: true); final fakeStatusRepo = FakeControlPlaneStatusRepository(); await tester.pumpWidget( IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byTooltip('Runtime')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); fakeStatusRepo.emptyOperationsEdgeId = 'edge-a'; fakeStatusRepo.fetchOperationsError = null; await _reFetchOperations(tester); expect(find.text('No operation executions recorded.'), findsOneWidget); fakeStatusRepo.fetchOperationsError = Exception('repository failure'); fakeStatusRepo.emptyOperationsEdgeId = null; await _reFetchOperations(tester); expect(find.textContaining('Error:'), findsOneWidget); expect( find.textContaining('repository failure'), findsOneWidget, ); }, ); } void _verifyPanelHeaderAndSections(WidgetTester tester) { expect(find.text('Operations & Domain Agents'), findsOneWidget); expect(find.text('Domain Agents'), findsOneWidget); expect(find.text('Operations Execution History'), findsOneWidget); expect(find.text('System Gated Operations'), findsOneWidget); } void _verifyDomainAgentsSummary(WidgetTester tester) { expect(find.text('DEPLOYER'), findsOneWidget); expect(find.text('BUILD-DEPLOY'), findsOneWidget); expect( find.text('READY'), findsNWidgets(2), ); } void _verifyActiveCommandPresence(WidgetTester tester) { expect(find.text('Active Command: cmd-alpha-active'), findsOneWidget); } void _verifyActiveCommandAbsence(WidgetTester tester) { expect(find.text('Active Command: cmd-beta-active'), findsNothing); expect(find.text('build-deploy.deploy'), findsNothing); expect(find.textContaining('Deploy queued for edge beta'), findsNothing); } void _verifyOperationsHistoryItem(WidgetTester tester) { expect(find.text('deployer.sync'), findsOneWidget); expect(find.text('SUCCESS'), findsOneWidget); expect(find.textContaining('Synced 5 models'), findsOneWidget); } void _verifyTriggerSyncAbsent(WidgetTester tester) { expect(find.text('Trigger Sync'), findsNothing); } void _verifySystemGatedButtons(WidgetTester tester) { expect(find.text('Health Check'), findsOneWidget); expect(find.text('Agent Status'), findsOneWidget); expect(find.text('Agent Command'), findsOneWidget); } Future _verifyHealthCheckSuccess( WidgetTester tester, FakeControlPlaneStatusRepository fakeStatusRepo, ) async { await tester.tap(find.text('Health Check')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.textContaining('Success: Command accepted'), findsOneWidget); expect(fakeStatusRepo.lastCommandEdgeId, equals('edge-a')); } void _verifyEdgeSwitchNotLoaded( WidgetTester tester, ) { expect(find.text('Active Command: cmd-alpha-active'), findsNothing); expect(find.text('Active Command: cmd-beta-active'), findsOneWidget); expect(find.text('deployer.sync'), findsNothing); expect(find.textContaining('Synced 5 models'), findsNothing); expect(find.byType(CircularProgressIndicator), findsOneWidget); } void _verifyEdgeBetaAgents(WidgetTester tester) { expect(find.text('BUSY'), findsOneWidget); expect(find.text('READY'), findsOneWidget); } void _verifyEdgeBetaActiveCommand(WidgetTester tester) { expect(find.text('Active Command: cmd-beta-active'), findsOneWidget); expect(find.text('build-deploy.deploy'), findsOneWidget); expect( find.textContaining('Deploy queued for edge beta'), findsOneWidget, ); } void _verifyEdgeBetaOperationsHistory(WidgetTester tester) { expect(find.text('Active Command: cmd-beta-active'), findsOneWidget); } void _verifyEdgeBetaActiveCommandAbsence(WidgetTester tester) { expect(find.text('Active Command: cmd-alpha-active'), findsNothing); } void _verifyEdgeBetaOperationsAbsence(WidgetTester tester) { expect(find.text('deployer.sync'), findsNothing); expect(find.textContaining('Synced 5 models'), findsNothing); } Future _verifyAgentStatusGating( WidgetTester tester, FakeControlPlaneStatusRepository fakeStatusRepo, ) async { await tester.tap(find.text('Agent Status')); await tester.pump(); expect(find.text('Get Agent Status'), findsOneWidget); await tester.enterText(find.byType(TextField), ''); await tester.tap(find.text('Send')); await tester.pump(); expect(find.text('Get Agent Status'), findsOneWidget); expect(fakeStatusRepo.lastCommandOperation, isNull); await tester.tap(find.text('Cancel')); await tester.pump(); } Future _verifyAgentCommandGating( WidgetTester tester, FakeControlPlaneStatusRepository fakeStatusRepo, ) async { await tester.tap(find.text('Agent Command')); await tester.pump(); expect(find.text('Send Agent Command'), findsOneWidget); await tester.tap(find.text('Send')); await tester.pump(); expect(find.text('Send Agent Command'), findsOneWidget); expect(fakeStatusRepo.lastCommandOperation, isNull); await tester.enterText(find.byType(TextField).at(0), 'node-1'); await tester.tap(find.text('Send')); await tester.pump(); expect(find.text('Send Agent Command'), findsOneWidget); expect(fakeStatusRepo.lastCommandOperation, isNull); await tester.enterText(find.byType(TextField).at(0), ''); await tester.enterText(find.byType(TextField).at(1), 'sync'); await tester.tap(find.text('Send')); await tester.pump(); expect(find.text('Send Agent Command'), findsOneWidget); expect(fakeStatusRepo.lastCommandOperation, isNull); await tester.tap(find.text('Cancel')); await tester.pump(); } Future _reFetchOperations( WidgetTester tester, ) async { await tester.tap(find.byIcon(Icons.refresh)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); }