import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:revoltvpn/logic/secure_socks_session.dart'; void main() { test('secure SOCKS session owns the entire inbound surface', () async { final session = await SecureSocksSession.create(jsonEncode({ 'inbounds': [ { 'tag': 'remote-listener', 'listen': '0.0.0.0', 'port': 9999, 'protocol': 'dokodemo-door', }, { 'tag': 'old-http', 'listen': '127.0.0.1', 'port': 8080, 'protocol': 'http', }, ], 'outbounds': [ {'tag': 'proxy', 'protocol': 'freedom'} ], })); final decoded = jsonDecode(session.configJson) as Map; final inbounds = decoded['inbounds'] as List; expect(inbounds, hasLength(1)); final inbound = inbounds.single as Map; expect(inbound['tag'], SecureSocksSession.inboundTag); expect(inbound['listen'], '127.0.0.1'); expect(inbound['protocol'], 'socks'); expect(inbound['port'], session.port); final settings = inbound['settings'] as Map; expect(settings['auth'], 'password'); final users = settings['users'] as List; expect(users, hasLength(1)); final account = users.single as Map; expect(account['user'], session.username); expect(account['pass'], session.password); }); test('vendored native source keeps full-tunnel and private IPC invariants', () { const root = 'local_packages/flutter_vless_android-1.1.5/android/src/main'; final service = File( '$root/kotlin/com/github/tfox/flutter_vless/xray/service/XrayVPNService.kt', ).readAsStringSync(); final core = File( '$root/kotlin/com/github/tfox/flutter_vless/xray/core/XrayCoreManager.kt', ).readAsStringSync(); final plugin = File( '$root/kotlin/com/github/tfox/flutter_vless/FlutterVlessPlugin.kt', ).readAsStringSync(); final manifest = File('$root/AndroidManifest.xml').readAsStringSync(); expect(service, contains('builder.addRoute("0.0.0.0", 0)')); expect(service, contains('builder.addRoute("::", 0)')); expect(service, contains('Android refused to establish VPN interface')); expect(service, contains('Per-app VPN bypass is disabled in ReVolt')); expect(service, contains('Failed to configure VPN DNS')); expect(service, contains('reader.forEachLine { _ -> }')); expect(plugin, contains('ContextCompat.RECEIVER_NOT_EXPORTED')); expect(core, contains('.setPackage(context.packageName)')); expect(core, contains('REVOLT_VPN_SERVICE')); expect(core, contains('NotificationManager.IMPORTANCE_LOW')); expect(core, contains('deleteEphemeralConfigOnFailure')); expect(core, contains('reader.forEachLine { _ -> }')); expect(manifest, contains('android:exported="false"')); expect(manifest, contains('android:permission="android.permission.BIND_VPN_SERVICE"')); expect(manifest, contains('android.net.VpnService.SUPPORTS_ALWAYS_ON')); expect(manifest, contains('android:value="false"')); expect(manifest, isNot(contains('android.permission.CHANGE_NETWORK_STATE'))); expect(manifest, isNot(contains('android.permission.READ_EXTERNAL_STORAGE'))); }); test('native app bridge keeps runtime adoption on private heartbeat only', () { final source = File( 'android/app/src/main/kotlin/com/paladinvpn/app/MainActivity.kt', ).readAsStringSync(); final vpnSource = File('lib/logic/vpn_connection.dart').readAsStringSync(); final powerSource = File('lib/logic/power_settings.dart').readAsStringSync(); expect(source, isNot(contains('openVpnSettings'))); expect(source, isNot(contains('ACTION_VPN_SETTINGS'))); expect(source, isNot(contains('FROM_DISCONNECT_BTN'))); expect(source, isNot(contains('isVpnRuntimeAlive'))); expect(source, isNot(contains('isVpnServiceProcessAlive'))); expect(source, contains('REVOLT_VPN_SERVICE')); expect(vpnSource, contains('if (_connectEpoch == 0) _adoptedRunningRuntime = true;')); expect(vpnSource, isNot(contains('PowerSettings.runtimeState'))); expect(vpnSource, isNot(contains('_adoptRunningRuntime'))); expect(powerSource, isNot(contains('runtimeState'))); expect(powerSource, isNot(contains('VpnRuntimeState'))); }); test('configured AdMob bypass contract remains intact', () { final source = File('lib/logic/hivemind_service.dart').readAsStringSync(); expect(source, contains('await _runConfiguredBypass(deviceId, nonce);')); expect( source, contains('/admob/callback?signature=test&key_id=test&custom_data='), ); }); test('local SOCKS diagnostics cannot fall back to unauthenticated legacy mode', () { final source = File('lib/logic/local_socks_tester.dart').readAsStringSync(); expect(source, contains('required int port')); expect(source, contains('required String username')); expect(source, contains('required String password')); expect(source, contains('const [0x05, 0x01, 0x02]')); expect(source, isNot(contains('const [0x05, 0x01, 0x00]'))); expect(source, isNot(contains('int port = 10807'))); expect(source, isNot(contains('paladinvpn.duckdns.org'))); expect(source, contains("String targetHost = 'example.com'")); expect(source, contains('const totalTimeout = Duration(seconds: 8);')); }); test('hivemind rejects missing nonce and stale session responses', () { final source = File('lib/logic/hivemind_service.dart').readAsStringSync(); expect(source, contains('_expectedNonce != nonce ||')); expect(source, contains('serverNonce is! String ||')); expect( source, contains( 'final session = await _fetchActiveSession(deviceId, nonce);\n' ' _throwIfCancelled(callId);', ), ); }); test('hivemind validates and pins session fields before constructing VLESS', () { final source = File('lib/logic/hivemind_service.dart').readAsStringSync(); expect(source, contains('_uuidPattern')); expect(source, contains('_maxControlResponseBytes = 256 * 1024')); expect(source, contains('controlGet(')); expect(source, contains('_validatedPublicIp(AppConfig.serverIp')); expect(source, contains('const port = 443;')); expect(source, contains("_requiredString(json, 'reality_pbk', maxLength: 128)")); expect(source, contains('_validatedHost(')); expect( source, contains("final pinnedHost = _validatedPublicIp(AppConfig.serverIp, 'serverIp');"), ); expect(source, contains('advertisedHost.trim() != pinnedHost')); expect( source, isNot(contains("_stringOr(json['vless_ip'], AppConfig.serverIp)")), ); expect(source, contains('return Uri(')); expect(source, contains('queryParameters: {')); expect(source, isNot(contains("return 'vless://\$uuid@\$host:\$port'"))); }); test('VPN connect work cannot outlive its generation', () { final source = File('lib/logic/vpn_connection.dart').readAsStringSync(); expect(source, contains('int _connectEpoch = 0;')); expect(source, contains('final connectEpoch = ++_connectEpoch;')); expect(source, contains('epoch == _connectEpoch && !_userDisconnecting')); expect(source, contains('_connectEpoch++;\n _suppressNativeConnect = true;')); expect(source, contains('if (_suppressNativeConnect || _userDisconnecting) return;')); expect(source, isNot(contains('bool _cancelled = false;'))); }); test('native recovery is fail-closed without hot process restart loops', () { const root = 'local_packages/flutter_vless_android-1.1.5/android/src/main/kotlin/com/github/tfox/flutter_vless'; final service = File('$root/xray/service/XrayVPNService.kt').readAsStringSync(); final core = File('$root/xray/core/XrayCoreManager.kt').readAsStringSync(); expect(service, contains('scheduleTun2socksRecovery')); expect(service, contains('tun2socksRecoveryAttempt')); expect(service, contains('coerceAtMost(30_000L)')); expect(service, contains('recoveringTun2socks = false')); expect(service, contains('tun2socksRecoveryAttempt = 0')); expect(service, contains('XrayCoreManager.markRuntimeConnecting(this, config)')); expect(service, isNot(contains('Thread.sleep(350)'))); expect(service, isNot(contains('Thread.sleep(600)'))); expect( service, isNot(contains('attempt = 0\n Thread.sleep(3000)')), ); expect(core, contains('context.handleXrayCoreExit(config)')); expect(core, contains('AppConfigs.RUNTIME_READY = false')); }); test('data disclosure only persists an explicit acknowledged action', () { final source = File('lib/components/data_disclosure_dialog.dart').readAsStringSync(); expect(source, contains('final accepted = await showDialog(')); expect(source, contains('if (accepted != true) return;')); expect(source, contains('final saved = await prefs.setBool(_key, true);')); expect(source, contains('if (!saved)')); expect(source, contains('Navigator.of(context).pop(true)')); expect(source, isNot(contains('prefs.setBool(_key, true);\n Navigator'))); }); test('unsupported server picker is not wired into app state', () { final mainSource = File('lib/main.dart').readAsStringSync(); final statusBar = File('lib/components/status_bar.dart').readAsStringSync(); expect(mainSource, isNot(contains('ServerList'))); expect(statusBar, isNot(contains('SelectionScreen'))); }); test('session timer rejects stale session work by generation', () { final source = File('lib/logic/session_timer.dart').readAsStringSync(); expect(source, contains('_sessionEpoch')); expect(source, contains('Stopwatch')); expect(source, contains("_doDisconnect('VPN entered error state')")); expect(source, isNot(contains('_stopForVpnFailure'))); }); }