iop/apps/client/test/runtime_panel_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

242 lines
9.6 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() {
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));
// Verify panel header and sections
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);
// Verify domain agents summary listed
expect(find.text('DEPLOYER'), findsOneWidget);
expect(find.text('BUILD-DEPLOY'), findsOneWidget);
expect(
find.text('READY'),
findsNWidgets(2),
); // deployer and build-deploy are both ready
// [REVIEW_API-2] Verify domain agents active command summary for Edge Alpha (presence)
expect(find.text('Active Command: cmd-alpha-active'), findsOneWidget);
// [REVIEW_API-2] Verify beta active command summary is NOT showing for Edge Alpha
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);
// Verify operations history item
expect(find.text('deployer.sync'), findsOneWidget);
expect(find.text('SUCCESS'), findsOneWidget);
expect(find.textContaining('Synced 5 models'), findsOneWidget);
// Verify Trigger Sync button is not present
expect(find.text('Trigger Sync'), findsNothing);
// Verify System Gated buttons are present
expect(find.text('Health Check'), findsOneWidget);
expect(find.text('Agent Status'), findsOneWidget);
expect(find.text('Agent Command'), findsOneWidget);
// Verify Health Check button works and shows success banner
await tester.tap(find.text('Health Check'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Check for success status banner message
expect(find.textContaining('Success: Command accepted'), findsOneWidget);
// Verify command target edge ID was edge-a
expect(fakeStatusRepo.lastCommandEdgeId, equals('edge-a'));
// Setup delayed fetch for edge-b
fakeStatusRepo.edgeBOperationsCompleter =
Completer<EdgeOperationsResponseView>();
// Switch to Edge Beta via Dropdown
await tester.tap(find.byType(DropdownButton<String>));
await tester.pumpAndSettle();
await tester.tap(find.text('Edge Beta').last);
// Pump one frame to trigger edge selection but wait before operations fetch resolves
await tester.pump();
// [REVIEW_API-1] Verify that before the operations fetch completes:
// 1. The domain agents section has updated immediately (so cmd-alpha-active is gone, cmd-beta-active is present)
expect(find.text('Active Command: cmd-alpha-active'), findsNothing);
expect(find.text('Active Command: cmd-beta-active'), findsOneWidget);
// 2. The operations history of the previous edge (edge-a) is cleared immediately upon fetch start.
expect(find.text('deployer.sync'), findsNothing);
expect(find.textContaining('Synced 5 models'), findsNothing);
// 3. The loading indicator is shown since _operations is null and _isLoading is true
expect(find.byType(CircularProgressIndicator), findsOneWidget);
// Now complete the operations fetch for edge-b
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();
// Verify domain agents for Edge Beta (deployer shows BUSY, build-deploy shows READY)
expect(find.text('BUSY'), findsOneWidget);
expect(find.text('READY'), findsOneWidget);
// [REVIEW_API-2] Verify presence of beta active command and operations history
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,
);
// [REVIEW_API-2] Verify absence of alpha-only active command and operations history
expect(find.text('Active Command: cmd-alpha-active'), findsNothing);
expect(find.text('deployer.sync'), findsNothing);
expect(find.textContaining('Synced 5 models'), findsNothing);
// Verify command triggering on Edge Beta sends command to edge-b
await tester.tap(find.text('Health Check'));
await tester.pumpAndSettle();
expect(fakeStatusRepo.lastCommandEdgeId, equals('edge-b'));
},
);
testWidgets(
'Client App handles unsupported or error command responses and shows error banner',
(WidgetTester tester) async {
final fakeClient = FakeClientWireClient(shouldSuccess: true);
final fakeStatusRepo = FakeControlPlaneStatusRepository();
// Configure repository to simulate a failed/unsupported command response
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));
// Verify Agent Status button works, opens dialog, and then shows error banner
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));
// Check for failure status banner message
expect(
find.textContaining(
'Failed: unsupported - Operation not supported by Edge',
),
findsOneWidget,
);
},
);
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));
// 1. Verify agent.status gating
await tester.tap(find.text('Agent Status'));
await tester.pump();
expect(find.text('Get Agent Status'), findsOneWidget);
// Try to send with empty target selector
await tester.enterText(find.byType(TextField), '');
await tester.tap(find.text('Send'));
await tester.pump();
// The dialog should still be open (Send was gated/no-op)
expect(find.text('Get Agent Status'), findsOneWidget);
expect(fakeStatusRepo.lastCommandOperation, isNull);
// Cancel dialog
await tester.tap(find.text('Cancel'));
await tester.pump();
// 2. Verify agent.command gating
await tester.tap(find.text('Agent Command'));
await tester.pump();
expect(find.text('Send Agent Command'), findsOneWidget);
// Try to send with empty command name and empty selector
await tester.tap(find.text('Send'));
await tester.pump();
expect(find.text('Send Agent Command'), findsOneWidget);
expect(fakeStatusRepo.lastCommandOperation, isNull);
// Enter only selector
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);
// Enter only command name (clear selector)
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);
// Cancel
await tester.tap(find.text('Cancel'));
await tester.pump();
},
);
}