iop/apps/client/test/provider_status_test.dart
toki 01dc2ef78b refactor: readability baseline 및 테스트 구조 개선
- agent-readable-repository-refactor 완료: 기존 테스트 파일 아카이브 이동
- 새 테스트 파일 추가 (edge, node, client, config, readability)
- readability_audit 스크립트 및 baseline 추가
- roadmap/SDD 문서 갱신
- agent-client/pi/extensions/openai-sampling-parameters 추가
2026-07-17 16:02:12 +09:00

473 lines
17 KiB
Dart

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);
},
);
}