import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:sm_mobile/core/app_controller.dart'; import 'package:sm_mobile/main.dart'; import 'package:sm_mobile/models/server_profile.dart'; import 'package:sm_mobile/services/secure_store.dart'; import 'package:xterm/xterm.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets( 'native secure storage, explicit license, profile UI, OpenSSH and lock', (tester) async { final namespace = 'sm_integration_${newId()}'; final store = DeviceStore( FlutterSecureStorage( aOptions: AndroidOptions( resetOnError: false, storageNamespace: namespace, ), iOptions: IOSOptions( accountName: namespace, accessibility: KeychainAccessibility.unlocked_this_device, ), ), ); final c = AppController(store); Future until(bool Function() predicate) async { for (var i = 0; i < 600 && !predicate(); i++) { await tester.pump(const Duration(milliseconds: 100)); } expect(predicate(), true); } await c.initialize(); c.locale = const Locale('en'); await tester.pumpWidget(SmMobileApp(c)); await tester.pumpAndSettle(); expect(c.accepted, false); await tester.tap(find.text('Continue')); await tester.pumpAndSettle(); expect( tester .widget( find.widgetWithText(FilledButton, 'Accept and continue'), ) .onPressed, null, ); await tester.tap(find.byType(Checkbox)); await tester.pumpAndSettle(); await tester.tap(find.text('Accept and continue')); await until(() => c.accepted); await tester.pumpAndSettle(); await tester.enterText( find.byType(TextField).at(0), 'native integration master password', ); await tester.enterText( find.byType(TextField).at(1), 'native integration master password', ); await tester.tap( find.widgetWithText(FilledButton, 'Create secure Vault'), ); await until(() => c.vault.unlocked); await tester.pumpAndSettle(); await tester.tap(find.byTooltip('Add server')); await tester.pumpAndSettle(); final fields = find.byType(TextField); await tester.enterText(fields.at(0), 'Isolated device test'); const host = String.fromEnvironment( 'SM_TEST_HOST', defaultValue: '127.0.0.1', ); await tester.enterText(fields.at(1), host); await tester.enterText( fields.at(2), const String.fromEnvironment('SM_TEST_USER', defaultValue: 'test'), ); await tester.enterText(fields.at(3), '22229'); FocusManager.instance.primaryFocus?.unfocus(); await tester.pumpAndSettle(); await tester.ensureVisible(find.widgetWithText(FilledButton, 'Save')); await tester.pumpAndSettle(); await tester.tap(find.widgetWithText(FilledButton, 'Save')); await until(() => c.vault.profiles.isNotEmpty); await tester.pumpAndSettle(); final p = c.vault.profiles.single; const keyB64 = String.fromEnvironment('SM_TEST_KEY_B64'); if (keyB64.isNotEmpty) { await c.vault.save( ServerProfile( id: p.id, name: p.name, host: p.host, username: p.username, port: p.port, auth: AuthMethod.privateKey, ), privateKey: utf8.decode(base64Decode(keyB64)), ); c.changed(); await tester.pumpAndSettle(); await tester.tap(find.text('Isolated device test')); await tester.pumpAndSettle(); await tester.enterText( find.byType(TextField).last, 'isolated-test-passphrase', ); await tester.tap(find.widgetWithText(FilledButton, 'Connect')); await until( () => find.text('Verify server identity').evaluate().isNotEmpty, ); const fingerprint = String.fromEnvironment('SM_TEST_FINGERPRINT'); expect( find.byWidgetPredicate( (w) => w is SelectableText && (w.data ?? '').contains(fingerprint), ), findsOneWidget, ); await tester.tap(find.text('Trust this key')); await until(() => c.ssh.connected); await tester.pumpAndSettle(); expect(find.byType(TerminalView), findsOneWidget); await tester.tap(find.text('Files').last); await tester.pumpAndSettle(); await until( () => find.byTooltip('Parent directory').evaluate().isNotEmpty, ); } expect( await store.read('vault'), isNot(contains('Isolated device test')), ); c.lock(); await tester.pumpAndSettle(); expect(c.ssh.connected, false); expect(c.vault.unlocked, false); expect(find.byType(TerminalView), findsNothing); await tester.enterText( find.byType(TextField).first, 'native integration master password', ); await tester.tap(find.widgetWithText(FilledButton, 'Unlock')); await until(() => c.vault.unlocked); await tester.pumpAndSettle(); expect(c.vault.profiles.single.name, 'Isolated device test'); await tester.pumpWidget(const SizedBox()); c.dispose(); for (final key in ['vault', 'consent', 'preferences']) { await store.delete(key); } }, timeout: const Timeout(Duration(minutes: 3)), ); }