iop/apps/client/test/runtime_panel_test.dart

379 lines
12 KiB
Dart

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<EdgeOperationsResponseView>();
await tester.tap(find.byType(DropdownButton<String>));
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<EdgeCommandResponseView>();
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<void> _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<void> _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<void> _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<void> _reFetchOperations(
WidgetTester tester,
) async {
await tester.tap(find.byIcon(Icons.refresh));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
}