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 'package:iop_client/widgets/nodes_panel.dart'; import 'support/client_test_harness.dart'; void main() { // [REVIEW_API-1] Regression: health=degraded, status=active -> yellow state text/color testWidgets( 'Provider with health=degraded and status=active shows DEGRADED text with yellow color', (WidgetTester tester) async { final fakeClient = FakeClientWireClient(shouldSuccess: true); final fakeStatusRepo = FakeControlPlaneStatusRepository(); // Override the OpenAI provider to have health=degraded, status=active // This is already set in the fake repo, but verify via the widget await tester.pumpWidget( IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); await tester.tap(find.byTooltip('Nodes')); await tester.pumpAndSettle(); // Scroll to reveal node-2's provider catalog await tester.drag(find.byType(ListView), const Offset(0.0, -1500.0)); await tester.pumpAndSettle(); // The degraded provider should show DEGRADED text (not ACTIVE) expect(find.textContaining('DEGRADED'), findsOneWidget); // Also confirm the provider id is shown expect(find.text('provider-openai'), findsOneWidget); }, ); // [REVIEW_API-2] DTO JSON parsing test testWidgets( 'EdgeStatusResponseView.fromJson parses raw JSON and validates provider_snapshots', (WidgetTester tester) async { // This test verifies the HTTP JSON DTO parsing path used by // HttpControlPlaneStatusRepository.fetchEdgeStatus. final rawJson = { 'request_id': 'test-req-001', 'edge_id': 'edge-test', 'edge_name': 'Edge Test Node', 'observed_time_unix_nano': 1716584400000000000, 'nodes': [ { 'node_id': 'node-1', 'alias': 'Test Node', 'label': 'GPU-A100', 'connected': true, 'config': { 'adapters': [ {'type': 'ollama', 'enabled': true}, ], 'concurrency': 8, }, 'provider_snapshots': [ { 'id': 'provider-ollama', 'adapter': 'ollama', 'type': 'llm', 'category': 'inference', 'status': 'active', 'health': 'degraded', 'capacity': 10, 'in_flight': 3, 'queued': 1, 'load_ratio': 0.3, 'served_models': ['llama-3.1', 'mistral'], 'lifecycle_capabilities': ['start', 'stop', 'restart'], }, { 'id': 'provider-vllm', 'adapter': 'vllm', 'type': 'llm', 'category': 'inference', 'status': 'ready', 'health': 'healthy', 'capacity': 20, 'in_flight': 0, 'queued': 0, 'load_ratio': 0.0, 'served_models': ['vicuna'], 'lifecycle_capabilities': ['start', 'stop'], }, ], }, ], 'capabilities': [ { 'kind': 'Serving', 'available': true, 'status': 'ready', 'summary': 'Active', }, ], 'domain_agents': [], 'metadata': {'region': 'us-west'}, 'error': '', }; // Parse the raw JSON using the DTO from // apps/client/lib/control_plane_status_dto.dart final parsed = EdgeStatusResponseView.fromJson(rawJson); // Validate top-level fields expect(parsed.requestId, equals('test-req-001')); expect(parsed.edgeId, equals('edge-test')); expect(parsed.edgeName, equals('Edge Test Node')); expect(parsed.observedTimeUnixNano, equals(1716584400000000000)); expect(parsed.nodes.length, equals(1)); expect(parsed.error, equals('')); // Validate node fields final node = parsed.nodes.first; expect(node.nodeId, equals('node-1')); expect(node.alias, equals('Test Node')); expect(node.label, equals('GPU-A100')); expect(node.connected, isTrue); expect(node.config, isNotNull); expect(node.config!.adapters.length, equals(1)); expect(node.config!.adapters.first.type, equals('ollama')); expect(node.config!.adapters.first.enabled, isTrue); expect(node.config!.concurrency, equals(8)); // Validate provider_snapshots fields final prov0 = node.providerSnapshots[0]; expect(prov0.id, equals('provider-ollama')); expect(prov0.adapter, equals('ollama')); expect(prov0.type, equals('llm')); expect(prov0.category, equals('inference')); expect(prov0.status, equals('active')); expect(prov0.health, equals('degraded')); expect(prov0.capacity, equals(10)); expect(prov0.inFlight, equals(3)); expect(prov0.queued, equals(1)); expect(prov0.loadRatio, equals(0.3)); expect(prov0.servedModels, equals(['llama-3.1', 'mistral'])); expect(prov0.lifecycleCapabilities, equals(['start', 'stop', 'restart'])); // Validate second provider snapshot final prov1 = node.providerSnapshots[1]; expect(prov1.id, equals('provider-vllm')); expect(prov1.adapter, equals('vllm')); expect(prov1.status, equals('ready')); expect(prov1.health, equals('healthy')); expect(prov1.capacity, equals(20)); expect(prov1.inFlight, equals(0)); expect(prov1.queued, equals(0)); expect(prov1.loadRatio, equals(0.0)); expect(prov1.servedModels, equals(['vicuna'])); expect(prov1.lifecycleCapabilities, equals(['start', 'stop'])); // Validate capabilities and metadata expect(parsed.capabilities.length, equals(1)); expect(parsed.capabilities.first.kind, equals('Serving')); expect(parsed.capabilities.first.available, isTrue); expect(parsed.metadata, equals({'region': 'us-west'})); // Verify that the same raw JSON can also be rendered in the widget. // This connects the DTO parsing path to the UI path. final statusView = EdgeStatusResponseView.fromJson(rawJson); await tester.pumpWidget( MaterialApp( home: Scaffold( body: NodesPanel( edges: [ FleetEdgeView( edgeId: 'edge-test', edgeName: 'Edge Test Node', version: '1.0.0', protocol: 'iop-wire-v1', connected: true, health: 'healthy', lastSeen: DateTime.now(), nodeCount: 1, capabilities: [], domainAgents: [], error: '', ), ], selectedEdgeId: 'edge-test', selectedEdgeStatus: statusView, isLoading: false, error: null, onSelectEdge: (_) {}, onRefresh: () {}, ), ), ), ); await tester.pump(); // Verify degraded provider shows correctly via _stateColor semantics expect(find.textContaining('DEGRADED'), findsOneWidget); expect(find.textContaining('HEALTHY'), findsOneWidget); expect(find.textContaining('provider-ollama'), findsOneWidget); expect(find.textContaining('provider-vllm'), findsOneWidget); }, ); // [G03] Control Plane predecessor values: health=available/status=available testWidgets( 'Provider with health=available and status=available shows AVAILABLE text with green color', (WidgetTester tester) async { // Test uses standalone NodesPanel with available providers. // The DTO test below covers JSON parsing for all predecessor values. final statusView = EdgeStatusResponseView.fromJson({ 'request_id': 'test-req-g03-solo', 'edge_id': 'edge-g03', 'edge_name': 'Edge G03 Test', 'observed_time_unix_nano': 1716584400000000000, 'nodes': [ { 'node_id': 'node-g03', 'alias': 'G03 Test Node', 'label': 'GPU-Test', 'connected': true, 'config': { 'adapters': [ {'type': 'ollama', 'enabled': true}, ], 'concurrency': 4, }, 'provider_snapshots': [ { 'id': 'provider-available', 'adapter': 'ollama', 'type': 'llm', 'category': 'inference', 'status': 'available', 'health': 'available', 'capacity': 10, 'in_flight': 2, 'queued': 0, 'load_ratio': 0.2, 'served_models': ['llama-3.1'], 'lifecycle_capabilities': ['start', 'stop'], }, ], }, ], 'capabilities': [], 'domain_agents': [], 'metadata': {}, 'error': '', }); await tester.pumpWidget( MaterialApp( home: Scaffold( body: NodesPanel( edges: [ FleetEdgeView( edgeId: 'edge-g03', edgeName: 'Edge G03 Test', version: '1.0.0', protocol: 'iop-wire-v1', connected: true, health: 'healthy', lastSeen: DateTime.now(), nodeCount: 1, capabilities: [], domainAgents: [], error: '', ), ], selectedEdgeId: 'edge-g03', selectedEdgeStatus: statusView, isLoading: false, error: null, onSelectEdge: (_) {}, onRefresh: () {}, ), ), ), ); await tester.pump(); // health=available/status=available should show AVAILABLE text in green (#10B981) expect(find.text('AVAILABLE'), findsOneWidget); expect(find.text('provider-available'), findsOneWidget); // [G02] Status label color assertions // Find the AVAILABLE status label Text widget (inside Container) and verify its color final availableLabelFinder = find.byWidgetPredicate((Widget widget) { if (widget is Text) { return widget.data == 'AVAILABLE' && widget.style != null; } return false; }); expect(availableLabelFinder, findsOneWidget); final availableLabel = availableLabelFinder.evaluate().first.widget as Text; expect(availableLabel.style?.color, equals(const Color(0xFF10B981))); }, ); // [G03] DTO JSON parsing test for Control Plane predecessor values testWidgets( 'EdgeStatusResponseView parses health=available/status=available and health=unavailable/status=backlog', (WidgetTester tester) async { final rawJson = { 'request_id': 'test-req-g03', 'edge_id': 'edge-g03', 'edge_name': 'Edge G03 Test', 'observed_time_unix_nano': 1716584400000000000, 'nodes': [ { 'node_id': 'node-g03', 'alias': 'G03 Test Node', 'label': 'GPU-Test', 'connected': true, 'config': { 'adapters': [ {'type': 'ollama', 'enabled': true}, ], 'concurrency': 4, }, 'provider_snapshots': [ { // health=available, status=available -> green 'id': 'provider-available', 'adapter': 'ollama', 'type': 'llm', 'category': 'inference', 'status': 'available', 'health': 'available', 'capacity': 10, 'in_flight': 2, 'queued': 0, 'load_ratio': 0.2, 'served_models': ['llama-3.1'], 'lifecycle_capabilities': ['start', 'stop'], }, { // health=unavailable, status=backlog -> red 'id': 'provider-unavailable', 'adapter': 'vllm', 'type': 'llm', 'category': 'inference', 'status': 'backlog', 'health': 'unavailable', 'capacity': 20, 'in_flight': 0, 'queued': 0, 'load_ratio': 0.0, 'served_models': [], 'lifecycle_capabilities': ['start', 'stop'], }, { // health=unknown, status=unknown -> neutral grey 'id': 'provider-unknown', 'adapter': 'openai', 'type': 'llm', 'category': 'external', 'status': 'unknown', 'health': '', 'capacity': 100, 'in_flight': 0, 'queued': 0, 'load_ratio': 0.0, 'served_models': ['gpt-4'], 'lifecycle_capabilities': [], }, ], }, ], 'capabilities': [], 'domain_agents': [], 'metadata': {}, 'error': '', }; // Parse the raw JSON final parsed = EdgeStatusResponseView.fromJson(rawJson); expect(parsed.requestId, equals('test-req-g03')); expect(parsed.edgeId, equals('edge-g03')); expect(parsed.nodes.length, equals(1)); // Validate provider snapshots final prov0 = parsed.nodes[0].providerSnapshots[0]; expect(prov0.id, equals('provider-available')); expect(prov0.health, equals('available')); expect(prov0.status, equals('available')); final prov1 = parsed.nodes[0].providerSnapshots[1]; expect(prov1.id, equals('provider-unavailable')); expect(prov1.health, equals('unavailable')); expect(prov1.status, equals('backlog')); final prov2 = parsed.nodes[0].providerSnapshots[2]; expect(prov2.id, equals('provider-unknown')); expect(prov2.health, equals('')); expect(prov2.status, equals('unknown')); // Verify that the same raw JSON can be rendered in the widget. final statusView = EdgeStatusResponseView.fromJson(rawJson); await tester.pumpWidget( MaterialApp( home: Scaffold( body: NodesPanel( edges: [ FleetEdgeView( edgeId: 'edge-g03', edgeName: 'Edge G03 Test', version: '1.0.0', protocol: 'iop-wire-v1', connected: true, health: 'healthy', lastSeen: DateTime.now(), nodeCount: 1, capabilities: [], domainAgents: [], error: '', ), ], selectedEdgeId: 'edge-g03', selectedEdgeStatus: statusView, isLoading: false, error: null, onSelectEdge: (_) {}, onRefresh: () {}, ), ), ), ); await tester.pump(); // Verify available provider shows AVAILABLE text expect(find.text('AVAILABLE'), findsWidgets); expect(find.textContaining('provider-available'), findsOneWidget); // [G02] UNAVAILABLE status label color assertion (#EF4444 red) final unavailableLabelFinder = find.byWidgetPredicate((Widget widget) { if (widget is Text) { return widget.data == 'UNAVAILABLE' && widget.style != null; } return false; }); expect(unavailableLabelFinder, findsOneWidget); final unavailableLabel = unavailableLabelFinder.evaluate().first.widget as Text; expect(unavailableLabel.style?.color, equals(const Color(0xFFEF4444))); // [G02] UNKNOWN status label color assertion (#64748B grey) // For provider-unknown, health='' so status='unknown' -> _stateColor returns Color(0xFF64748B) // displayState = p.health.isNotEmpty ? p.health : p.status = 'unknown' // displayState.toUpperCase() = 'UNKNOWN' final unknownLabelFinder = find.byWidgetPredicate((Widget widget) { if (widget is Text) { return widget.data == 'UNKNOWN' && widget.style != null; } return false; }); expect(unknownLabelFinder, findsOneWidget); final unknownLabel = unknownLabelFinder.evaluate().first.widget as Text; expect(unknownLabel.style?.color, equals(const Color(0xFF64748B))); // Verify unavailable provider shows UNAVAILABLE text expect(find.text('UNAVAILABLE'), findsWidgets); expect(find.textContaining('provider-unavailable'), findsOneWidget); // Verify unknown provider shows UNKNOWN text (from status) expect(find.text('UNKNOWN'), findsWidgets); expect(find.textContaining('provider-unknown'), findsOneWidget); }, ); }