xworkmate-app/test/features/settings_ai_gateway_persistence_suite.dart

151 lines
5.3 KiB
Dart

@TestOn('vm')
library;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:xworkmate/app/app_controller.dart';
import 'package:xworkmate/features/settings/settings_page.dart';
import 'package:xworkmate/runtime/secure_config_store.dart';
import '../test_support.dart';
void main() {
testWidgets(
'SettingsPage AI Gateway draft/save/apply flow persists edited fields through the global actions',
(WidgetTester tester) async {
late _AiGatewaySettingsTestController controller;
await tester.runAsync(() async {
SharedPreferences.setMockInitialValues(<String, Object>{});
final testRoot =
'${Directory.systemTemp.path}/xworkmate-widget-tests-${DateTime.now().microsecondsSinceEpoch}';
controller = _AiGatewaySettingsTestController(
store: SecureConfigStore(
enableSecureStorage: false,
databasePathResolver: () async => '$testRoot/settings.sqlite3',
fallbackDirectoryPathResolver: () async => testRoot,
),
);
await _waitFor(() => !controller.initializing);
});
addTearDown(controller.dispose);
final staleGateway = controller.settings.aiGateway.copyWith(
name: 'default',
baseUrl: '',
apiKeyRef: 'ai_gateway_api_key',
availableModels: const <String>['stale-model'],
selectedModels: const <String>['stale-model'],
syncState: 'invalid',
syncMessage: 'Missing AI Gateway URL',
);
await tester.runAsync(() async {
await controller.saveSettings(
controller.settings.copyWith(
aiGateway: staleGateway,
multiAgent: controller.settings.multiAgent.copyWith(
autoSync: false,
),
),
refreshAfterSave: false,
);
});
await pumpPage(
tester,
child: SettingsPage(controller: controller),
);
await tester.tap(find.text('集成'));
await tester.pump(const Duration(milliseconds: 300));
await tester.enterText(
find.byKey(const ValueKey('ai-gateway-name-field')),
'default',
);
await tester.enterText(
find.byKey(const ValueKey('ai-gateway-url-field')),
'https://api.svc.plus/v1',
);
await tester.enterText(
find.byKey(const ValueKey('ai-gateway-api-key-ref-field')),
'ai_gateway_api_key',
);
expect(
tester
.widget<TextField>(
find.byKey(const ValueKey('ai-gateway-url-field')),
)
.controller!
.text,
'https://api.svc.plus/v1',
);
expect(find.byKey(const ValueKey('ai-gateway-save-button')), findsOneWidget);
expect(find.byKey(const ValueKey('ai-gateway-apply-button')), findsOneWidget);
expect(
find.byKey(const ValueKey('settings-global-save-button')),
findsOneWidget,
);
expect(
find.byKey(const ValueKey('settings-global-apply-button')),
findsOneWidget,
);
expect(controller.settingsDraft.aiGateway.baseUrl, 'https://api.svc.plus/v1');
expect(controller.settings.aiGateway.baseUrl, isEmpty);
final saveButton = tester.widget<OutlinedButton>(
find.byKey(const ValueKey('ai-gateway-save-button')),
);
await tester.runAsync(() async {
saveButton.onPressed!.call();
await _waitFor(() => controller.hasPendingSettingsApply);
});
await tester.pump(const Duration(milliseconds: 300));
expect(controller.hasPendingSettingsApply, isTrue);
expect(controller.settings.aiGateway.baseUrl, 'https://api.svc.plus/v1');
final applyButton = tester.widget<FilledButton>(
find.byKey(const ValueKey('ai-gateway-apply-button')),
);
await tester.runAsync(() async {
applyButton.onPressed!.call();
await _waitFor(() => !controller.hasPendingSettingsApply);
});
await tester.pump(const Duration(milliseconds: 300));
expect(controller.settings.aiGateway.name, 'default');
expect(controller.settings.aiGateway.baseUrl, 'https://api.svc.plus/v1');
expect(controller.settings.aiGateway.apiKeyRef, 'ai_gateway_api_key');
expect(controller.settings.aiGateway.availableModels, isEmpty);
expect(controller.settings.aiGateway.selectedModels, isEmpty);
expect(controller.settings.aiGateway.syncState, 'idle');
expect(controller.settings.aiGateway.syncMessage, 'Ready to sync models');
expect(controller.hasPendingSettingsApply, isFalse);
expect(find.text('Missing AI Gateway URL'), findsNothing);
expect(find.text('Ready to sync models'), findsOneWidget);
},
);
}
class _AiGatewaySettingsTestController extends AppController {
_AiGatewaySettingsTestController({super.store});
@override
Future<void> refreshMultiAgentMounts({bool sync = false}) async {}
}
Future<void> _waitFor(bool Function() predicate) async {
final deadline = DateTime.now().add(const Duration(seconds: 10));
while (!predicate()) {
if (DateTime.now().isAfter(deadline)) {
throw StateError('condition not met before timeout');
}
await Future<void>.delayed(const Duration(milliseconds: 20));
}
}