@TestOn('vm') library; import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:xworkmate/runtime/mode_switcher.dart'; import 'package:xworkmate/runtime/gateway_runtime.dart'; import 'package:xworkmate/runtime/device_identity_store.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; import '../test_support.dart'; // Mock GatewayRuntime for testing class MockGatewayRuntime extends GatewayRuntime { factory MockGatewayRuntime() { final store = createIsolatedTestStore(); return MockGatewayRuntime._(store); } MockGatewayRuntime._(SecureConfigStore store) : super(store: store, identityStore: DeviceIdentityStore(store)); final StreamController _eventsController = StreamController.broadcast(); GatewayConnectionSnapshot _snapshot = GatewayConnectionSnapshot.initial(); bool _isConnected = false; final List> _requests = []; final Set _failingModes = {}; void failNextConnectFor(RuntimeConnectionMode mode) { _failingModes.add(mode); } void setConnected(bool connected) { _isConnected = connected; _snapshot = _snapshot.copyWith( status: connected ? RuntimeConnectionStatus.connected : RuntimeConnectionStatus.offline, statusText: connected ? 'Connected' : 'Offline', ); notifyListeners(); // Emit connection event if (connected) { unawaited( Future.delayed(Duration.zero, () { _eventsController.add( const GatewayPushEvent( event: 'gateway/connected', payload: {}, ), ); }), ); } } @override bool get isConnected => _isConnected; @override GatewayConnectionSnapshot get snapshot => _snapshot; @override Stream get events => _eventsController.stream; @override Future request( String method, { Map? params, Duration timeout = const Duration(seconds: 15), }) async { _requests.add({'method': method, 'params': params ?? const {}}); return {'success': true}; } @override Future initialize() async {} @override Future connectProfile( GatewayConnectionProfile profile, { int? profileIndex, String authTokenOverride = '', String authPasswordOverride = '', }) async { if (_failingModes.remove(profile.mode)) { throw StateError('Failed to connect ${profile.mode.name}'); } _isConnected = true; _snapshot = GatewayConnectionSnapshot.initial(mode: profile.mode).copyWith( status: RuntimeConnectionStatus.connected, statusText: 'Connected', ); notifyListeners(); unawaited( Future.delayed(Duration.zero, () { _eventsController.add( const GatewayPushEvent( event: 'gateway/connected', payload: {}, ), ); }), ); } @override Future disconnect({bool clearDesiredProfile = true}) async { _isConnected = false; _snapshot = GatewayConnectionSnapshot.initial( mode: _snapshot.mode, ).copyWith(statusText: 'Offline'); notifyListeners(); } @override void dispose() { _eventsController.close(); super.dispose(); } } void main() { group('GatewayMode', () { test('has all expected modes', () { expect(GatewayMode.values, hasLength(3)); expect(GatewayMode.values, contains(GatewayMode.local)); expect(GatewayMode.values, contains(GatewayMode.remote)); expect(GatewayMode.values, contains(GatewayMode.offline)); }); }); group('ModeSwitcherState', () { test('has all expected states', () { expect(ModeSwitcherState.values, hasLength(6)); expect( ModeSwitcherState.values, contains(ModeSwitcherState.disconnected), ); expect(ModeSwitcherState.values, contains(ModeSwitcherState.connecting)); expect( ModeSwitcherState.values, contains(ModeSwitcherState.connectedLocal), ); expect( ModeSwitcherState.values, contains(ModeSwitcherState.connectedRemote), ); expect(ModeSwitcherState.values, contains(ModeSwitcherState.offline)); expect(ModeSwitcherState.values, contains(ModeSwitcherState.error)); }); }); group('ModeCapabilities', () { test('local mode has correct capabilities', () { expect(ModeCapabilities.local.hasCloudMemory, isFalse); expect(ModeCapabilities.local.hasTaskQueue, isFalse); expect(ModeCapabilities.local.hasMultiAgent, isFalse); expect(ModeCapabilities.local.hasLocalModels, isTrue); expect(ModeCapabilities.local.hasCodeAgent, isTrue); }); test('remote mode has correct capabilities', () { expect(ModeCapabilities.remote.hasCloudMemory, isTrue); expect(ModeCapabilities.remote.hasTaskQueue, isTrue); expect(ModeCapabilities.remote.hasMultiAgent, isTrue); expect(ModeCapabilities.remote.hasLocalModels, isTrue); expect(ModeCapabilities.remote.hasCodeAgent, isTrue); }); test('offline mode has correct capabilities', () { expect(ModeCapabilities.offline.hasCloudMemory, isFalse); expect(ModeCapabilities.offline.hasTaskQueue, isFalse); expect(ModeCapabilities.offline.hasMultiAgent, isFalse); expect(ModeCapabilities.offline.hasLocalModels, isFalse); expect(ModeCapabilities.offline.hasCodeAgent, isTrue); }); test('toMap returns correct values', () { final map = ModeCapabilities.remote.toMap(); expect(map['hasCloudMemory'], isTrue); expect(map['hasTaskQueue'], isTrue); expect(map['hasMultiAgent'], isTrue); expect(map['hasLocalModels'], isTrue); expect(map['hasCodeAgent'], isTrue); }); }); group('ModeSwitchResult', () { test('success result is created correctly', () { final result = ModeSwitchResult( success: true, mode: GatewayMode.remote, capabilities: ModeCapabilities.remote.toMap(), ); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.remote)); expect(result.error, isNull); expect(result.capabilities, isNotNull); }); test('failure result is created correctly', () { final result = ModeSwitchResult( success: false, mode: GatewayMode.local, error: 'Connection failed', ); expect(result.success, isFalse); expect(result.mode, equals(GatewayMode.local)); expect(result.error, equals('Connection failed')); expect(result.capabilities, isNull); }); }); group('ModeSwitcher', () { late MockGatewayRuntime mockGateway; late ModeSwitcher modeSwitcher; setUp(() { mockGateway = MockGatewayRuntime(); modeSwitcher = ModeSwitcher(mockGateway); }); test('initial state is disconnected', () { expect(modeSwitcher.state, equals(ModeSwitcherState.disconnected)); expect(modeSwitcher.currentMode, equals(GatewayMode.offline)); expect(modeSwitcher.lastError, isNull); }); test('switchToLocal succeeds when gateway connects', () async { mockGateway.setConnected(true); final result = await modeSwitcher.switchToLocal(); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.local)); expect(modeSwitcher.state, equals(ModeSwitcherState.connectedLocal)); expect(modeSwitcher.capabilities.hasLocalModels, isTrue); }); test('switchToRemote succeeds when gateway connects', () async { mockGateway.setConnected(true); final result = await modeSwitcher.switchToRemote(); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.remote)); expect(modeSwitcher.state, equals(ModeSwitcherState.connectedRemote)); expect(modeSwitcher.capabilities.hasCloudMemory, isTrue); }); test('switchToOffline succeeds', () async { final result = await modeSwitcher.switchToOffline(); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.offline)); expect(modeSwitcher.state, equals(ModeSwitcherState.offline)); expect(modeSwitcher.capabilities.hasCloudMemory, isFalse); }); test('stateDescription returns correct values', () { expect(modeSwitcher.stateDescription, equals('Disconnected')); modeSwitcher.switchToLocal(); // Check after async completes Future.delayed(Duration(milliseconds: 100), () { expect( modeSwitcher.stateDescription, anyOf(equals('Connected (Local)'), equals('Connecting...')), ); }); }); test('modeDescription returns correct values', () { expect( modeSwitcher.modeDescription, equals('Offline Mode (Local Codex Only)'), ); }); test('autoSelect prefers remote by default', () async { mockGateway.setConnected(true); final result = await modeSwitcher.autoSelect(); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.remote)); }); test('autoSelect falls back to local when remote fails', () async { mockGateway.failNextConnectFor(RuntimeConnectionMode.remote); final result = await modeSwitcher.autoSelect(); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.local)); expect(modeSwitcher.currentMode, equals(GatewayMode.local)); }); test( 'autoSelect falls back to offline when remote and local fail', () async { mockGateway ..failNextConnectFor(RuntimeConnectionMode.remote) ..failNextConnectFor(RuntimeConnectionMode.local); final result = await modeSwitcher.autoSelect(); expect(result.success, isTrue); expect(result.mode, equals(GatewayMode.offline)); expect(modeSwitcher.currentMode, equals(GatewayMode.offline)); }, ); }); }