import "../mock_plugins.dart"; import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:xworkmate/app/app_controller.dart'; import 'package:xworkmate/runtime/account_runtime_client.dart'; import 'package:xworkmate/runtime/runtime_controllers.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); mockPlugins(); group('SettingsController account sync', () { test( 'prefers managed bridge token over stale profile token for remote gateway auth', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-sync-token-precedence-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults() .copyWith( selfHosted: const AcpBridgeServerSelfHostedConfig( serverUrl: 'https://xworkmate-bridge.svc.plus', username: 'ubuntu', passwordRef: 'legacy.bridge.token', ), ), ), ); await store.saveSecretValueByRef('legacy.bridge.token', 'stale-token'); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'managed-token', ); await store.saveAccountSessionToken('session-token'); await store.saveAccountSessionSummary( const AccountSessionSummary( userId: 'user-1', email: 'review@svc.plus', name: 'Review User', role: 'reviewer', mfaEnabled: true, ), ); await store.saveAccountSyncState( AccountSyncState.defaults().copyWith( syncState: 'ready', tokenConfigured: const AccountTokenConfigured( bridge: true, vault: false, ), ), ); final controller = AppController( store: store, environmentOverride: const {}, ); addTearDown(controller.dispose); await controller.settingsControllerInternal.initialize(); final token = await controller.settingsControllerInternal .loadEffectiveGatewayToken( profileIndex: kGatewayRemoteProfileIndex, ); expect(token, 'managed-token'); }, ); test( 'keeps using managed bridge token after the account sync state is blocked', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-sync-blocked-token-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', ), ); await store.saveAccountSessionToken('session-token'); await store.saveAccountSessionSummary( const AccountSessionSummary( userId: 'user-1', email: 'review@svc.plus', name: 'Review User', role: 'reviewer', mfaEnabled: true, ), ); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'managed-token', ); await store.saveAccountSyncState( AccountSyncState.defaults().copyWith( syncState: 'blocked', syncMessage: 'Bridge token expired or rejected.', tokenConfigured: const AccountTokenConfigured( bridge: true, vault: false, ), ), ); final controller = AppController( store: store, environmentOverride: const {}, ); addTearDown(controller.dispose); await controller.settingsControllerInternal.initialize(); final token = await controller.settingsControllerInternal .loadEffectiveGatewayToken( profileIndex: kGatewayRemoteProfileIndex, ); expect(token, 'managed-token'); }, ); test( 'updates in-memory blocked state when bridge authorization is unavailable', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-sync-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', ), ); await store.saveAccountSessionToken('session-token'); final client = _FakeAccountRuntimeClient( loginPayload: const {}, sessionPayload: const {}, syncPayload: const {}, ); final controller = SettingsController( store, accountClientFactory: (_) => client, ); addTearDown(controller.dispose); await controller.initialize(); final result = await controller.syncAccountSettings( baseUrl: 'https://accounts.svc.plus', ); expect(result.state, 'blocked'); expect(result.message, 'Bridge authorization is unavailable'); expect(controller.accountSyncState, isNotNull); expect(controller.accountSyncState!.syncState, 'blocked'); expect( controller.accountSyncState!.syncMessage, 'Bridge authorization is unavailable', ); expect(controller.accountSyncState!.profileScope, 'bridge'); expect( controller.accountSyncState!.lastSyncError, 'Bridge authorization is unavailable', ); expect(controller.accountStatus, 'Bridge authorization is unavailable'); expect(client.loadProfileCallCount, 1); expect(client.loadXWorkmateProfileSyncCallCount, 1); }, ); test( 'login sync stores managed bridge contract from protected profile sync', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-sync-uppercase-token-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', ), ); final controller = SettingsController( store, accountClientFactory: (_) => _FakeAccountRuntimeClient( loginPayload: { 'token': 'session-token', 'user': { 'id': 'user-1', 'email': 'review@svc.plus', }, }, syncPayload: const { 'AI_WORKSPACE_AUTH_TOKEN': 'ai-workspace-token-from-sync', 'BRIDGE_AUTH_TOKEN': 'bridge-token-from-sync', 'BRIDGE_SERVER_URL': 'https://xworkmate-bridge-alt.svc.plus', }, ), ); addTearDown(controller.dispose); await controller.initialize(); await controller.loginAccount( baseUrl: 'https://accounts.svc.plus', identifier: 'review@svc.plus', password: 'password', ); expect(controller.accountSyncState, isNotNull); expect(controller.accountSyncState!.syncState, 'ready'); expect( controller.accountSyncState!.syncedDefaults.bridgeServerUrl, 'https://xworkmate-bridge-alt.svc.plus', ); final persisted = await store.loadAccountSyncState(); expect(persisted, isNotNull); expect(persisted!.syncState, 'ready'); expect( persisted.syncedDefaults.bridgeServerUrl, 'https://xworkmate-bridge-alt.svc.plus', ); expect( controller .snapshot .acpBridgeServerModeConfig .cloudSynced .remoteServerSummary .endpoint, 'https://xworkmate-bridge-alt.svc.plus', ); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), 'ai-workspace-token-from-sync', ); expect( controller.snapshot.toJsonString().contains( 'ai-workspace-token-from-sync', ), isFalse, ); expect( jsonEncode( controller.accountSyncState!.toJson(), ).contains('ai-workspace-token-from-sync'), isFalse, ); }, ); test( 'login sync ignores bridge token fields outside protected profile sync', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-sync-legacy-token-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', ), ); final controller = SettingsController( store, accountClientFactory: (_) => _FakeAccountRuntimeClient( loginPayload: { 'token': 'session-token', 'BRIDGE_AUTH_TOKEN': 'bridge-token-from-login', 'BRIDGE_SERVER_URL': 'https://xworkmate-bridge-alt.svc.plus', 'user': { 'id': 'user-1', 'email': 'review@svc.plus', }, }, syncPayload: const {}, ), ); addTearDown(controller.dispose); await controller.initialize(); await controller.loginAccount( baseUrl: 'https://accounts.svc.plus', identifier: 'review@svc.plus', password: 'password', ); expect(controller.accountSyncState, isNotNull); expect(controller.accountSyncState!.syncState, 'blocked'); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), isNull, ); }, ); test( 'syncAccountSettings does not recover from stale managed bridge token', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-managed-bridge-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', accountUsername: 'review@svc.plus', assistantExecutionTarget: AssistantExecutionTarget.gateway, ), ); await store.saveAccountSessionToken('session-token'); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'bridge-token', ); final client = _FakeAccountRuntimeClient( loginPayload: const {}, sessionPayload: const {}, syncPayload: const {}, ); final controller = SettingsController( store, accountClientFactory: (_) => client, ); addTearDown(controller.dispose); await controller.initialize(); final result = await controller.syncAccountSettings( baseUrl: 'https://accounts.svc.plus', ); expect(result.state, 'blocked'); expect(controller.accountSyncState, isNotNull); expect(controller.accountSyncState!.syncState, 'blocked'); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), isNull, ); expect( controller.accountSyncState!.syncMessage, 'Bridge authorization is unavailable', ); expect(client.loadProfileCallCount, 1); expect(client.loadXWorkmateProfileSyncCallCount, 1); }, ); test( 'syncAccountSettings refreshes managed bridge metadata from protected account profile', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-managed-bridge-refresh-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', accountUsername: 'review@svc.plus', assistantExecutionTarget: AssistantExecutionTarget.gateway, ), ); await store.saveAccountSessionToken('session-token'); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'stale-bridge-token', ); final client = _FakeAccountRuntimeClient( loginPayload: const {}, sessionPayload: const { 'user': { 'id': 'user-1', 'email': 'review@svc.plus', }, }, syncPayload: { 'BRIDGE_AUTH_TOKEN': 'fresh-bridge-token', 'BRIDGE_SERVER_URL': 'https://xworkmate-bridge-new.svc.plus', }, ); final controller = SettingsController( store, accountClientFactory: (_) => client, ); addTearDown(controller.dispose); await controller.initialize(); final result = await controller.syncAccountSettings( baseUrl: 'https://accounts.svc.plus', ); expect(result.state, 'ready'); expect(client.loadProfileCallCount, 1); expect(client.loadXWorkmateProfileSyncCallCount, 1); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), 'fresh-bridge-token', ); expect( controller.snapshot.acpBridgeServerModeConfig.effective.source, 'cloud', ); expect( controller.snapshot.acpBridgeServerModeConfig.effective.endpoint, kManagedBridgeServerUrl, ); expect( controller.accountSyncState!.syncedDefaults.bridgeServerUrl, 'https://xworkmate-bridge-new.svc.plus', ); expect( controller.snapshot.assistantExecutionTarget, AssistantExecutionTarget.gateway, ); }, ); test( 'syncAccountSettings does not persist review token as managed bridge token', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-review-bridge-token-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', accountUsername: 'review@svc.plus', assistantExecutionTarget: AssistantExecutionTarget.gateway, ), ); await store.saveAccountSessionToken('session-token'); final client = _FakeAccountRuntimeClient( loginPayload: const {}, sessionPayload: const { 'user': { 'id': 'user-1', 'email': 'review@svc.plus', }, }, syncPayload: { 'BRIDGE_REVIEW_AUTH_TOKEN': 'review-bridge-token', 'BRIDGE_AUTH_TOKEN': 'multi-tenant-bridge-token', 'BRIDGE_SERVER_URL': 'https://xworkmate-bridge-review.svc.plus', }, ); final controller = SettingsController( store, accountClientFactory: (_) => client, ); addTearDown(controller.dispose); await controller.initialize(); final result = await controller.syncAccountSettings( baseUrl: 'https://accounts.svc.plus', ); expect(result.state, 'ready'); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), 'multi-tenant-bridge-token', ); expect(client.loadProfileCallCount, 1); expect(client.loadXWorkmateProfileSyncCallCount, 1); }, ); test( 'managed bridge endpoint stays fixed regardless of synced bridge url metadata', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-managed-bridge-runtime-', ); addTearDown(() async { if (await storeRoot.exists()) { try { await storeRoot.delete(recursive: true); } on FileSystemException { // Temp cleanup is best effort here. The controller may still be // releasing files when teardown starts. } } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveAccountSessionToken('session-token'); await store.saveAccountSessionSummary( const AccountSessionSummary( userId: 'user-1', email: 'review@svc.plus', name: 'Review User', role: 'reviewer', mfaEnabled: true, ), ); await store.saveAccountSyncState( AccountSyncState.defaults().copyWith( syncState: 'ready', syncedDefaults: AccountRemoteProfile.defaults().copyWith( bridgeServerUrl: 'https://xworkmate-bridge-alt.svc.plus', ), tokenConfigured: const AccountTokenConfigured( bridge: true, vault: false, ), ), ); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'bridge-token', ); final controller = AppController( environmentOverride: const {}, store: store, ); addTearDown(controller.dispose); await controller.settingsControllerInternal.initialize(); expect( controller.resolveGatewayAcpEndpointInternal()?.toString(), kManagedBridgeServerUrl, ); expect( await controller.resolveGatewayAcpAuthorizationHeaderInternal( Uri.parse('https://xworkmate-bridge-alt.svc.plus/acp/rpc'), ), isNull, ); expect( await controller.resolveGatewayAcpAuthorizationHeaderInternal( Uri.parse('$kManagedBridgeServerUrl/acp/rpc'), ), 'bridge-token', ); }, ); test( 'blocked managed bridge sync does not configure runtime authorization', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-managed-bridge-blocked-runtime-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveAccountSessionToken('session-token'); await store.saveAccountSessionSummary( const AccountSessionSummary( userId: 'user-1', email: 'review@svc.plus', name: 'Review User', role: 'reviewer', mfaEnabled: true, ), ); await store.saveAccountSyncState( AccountSyncState.defaults().copyWith( syncState: 'ready', tokenConfigured: const AccountTokenConfigured( bridge: true, vault: false, ), ), ); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'bridge-token', ); final controller = AppController( environmentOverride: const { 'BRIDGE_AUTH_TOKEN': 'env-token-must-not-recover-blocked-sync', }, store: store, ); addTearDown(controller.dispose); await controller.settingsControllerInternal.initialize(); await controller.settingsControllerInternal .markAccountBridgeRuntimeUnavailable( 'Bridge token expired or rejected. Please re-sync the account token.', ); expect(controller.isBridgeAcpRuntimeConfiguredInternal(), isFalse); expect( await controller.resolveGatewayAcpAuthorizationHeaderInternal( Uri.parse('$kManagedBridgeServerUrl/acp/rpc'), ), isNull, ); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), 'bridge-token', ); }, ); test('manual bridge config becomes the runtime ACP source', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-manual-bridge-runtime-', ); addTearDown(() async { if (await storeRoot.exists()) { try { await storeRoot.delete(recursive: true); } on FileSystemException { // Temp cleanup is best effort here. The controller may still be // releasing files when teardown starts. } } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults() .copyWith( selfHosted: AcpBridgeServerModeConfig.defaults().selfHosted .copyWith( serverUrl: 'https://private-bridge.svc.plus', username: 'admin', ), ), ), ); await store.saveSecretValueByRef( AcpBridgeServerSelfHostedConfig.defaults().passwordRef, 'manual-bridge-token', ); final controller = AppController( environmentOverride: const {}, store: store, ); addTearDown(controller.dispose); await controller.settingsControllerInternal.initialize(); expect( controller.resolveGatewayAcpEndpointInternal()?.toString(), 'https://private-bridge.svc.plus', ); expect(controller.isBridgeAcpRuntimeConfiguredInternal(), isTrue); expect( await controller.resolveGatewayAcpAuthorizationHeaderInternal( Uri.parse('https://private-bridge.svc.plus/acp/rpc'), ), 'manual-bridge-token', ); expect( await controller.resolveGatewayAcpAuthorizationHeaderInternal( Uri.parse('$kManagedBridgeServerUrl/acp/rpc'), ), isNull, ); }); test( 'manual bridge save accepts local loopback http with explicit token', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-manual-bridge-local-validation-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); final controller = SettingsController(store); addTearDown(controller.dispose); await controller.initialize(); final nextSettings = await controller.buildSavedAccountProfileSettings( settings: SettingsSnapshot.defaults(), accountBaseUrl: '', accountIdentifier: '', bridgeServerUrl: 'http://127.0.0.1:8787', bridgeToken: 'local-token', isManualBridge: true, ); expect( nextSettings.acpBridgeServerModeConfig.selfHosted.serverUrl, 'http://127.0.0.1:8787', ); expect( nextSettings.acpBridgeServerModeConfig.effective.source, 'bridge', ); expect( await store.loadSecretValueByRef( nextSettings.acpBridgeServerModeConfig.selfHosted.passwordRef, ), 'local-token', ); }, ); test( 'manual bridge save accepts localhost http with explicit token', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-manual-bridge-localhost-validation-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); final controller = SettingsController(store); addTearDown(controller.dispose); await controller.initialize(); final nextSettings = await controller.buildSavedAccountProfileSettings( settings: SettingsSnapshot.defaults(), accountBaseUrl: '', accountIdentifier: '', bridgeServerUrl: 'http://localhost:8787', bridgeToken: 'localhost-token', isManualBridge: true, ); expect( nextSettings.acpBridgeServerModeConfig.selfHosted.serverUrl, 'http://localhost:8787', ); expect( nextSettings.acpBridgeServerModeConfig.effective.source, 'bridge', ); expect( await store.loadSecretValueByRef( nextSettings.acpBridgeServerModeConfig.selfHosted.passwordRef, ), 'localhost-token', ); }, ); test( 'manual bridge save accepts public custom https bridge with token', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-manual-bridge-https-validation-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); final controller = SettingsController(store); addTearDown(controller.dispose); await controller.initialize(); final nextSettings = await controller.buildSavedAccountProfileSettings( settings: SettingsSnapshot.defaults(), accountBaseUrl: '', accountIdentifier: '', bridgeServerUrl: 'https://private-bridge.example.com', bridgeToken: 'public-token', isManualBridge: true, ); expect( nextSettings.acpBridgeServerModeConfig.selfHosted.serverUrl, 'https://private-bridge.example.com', ); expect( nextSettings.acpBridgeServerModeConfig.effective.source, 'bridge', ); expect( await store.loadSecretValueByRef( nextSettings.acpBridgeServerModeConfig.selfHosted.passwordRef, ), 'public-token', ); }, ); test('manual bridge save rejects non-local http bridge url', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-manual-bridge-http-reject-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); final controller = SettingsController(store); addTearDown(controller.dispose); await controller.initialize(); expect( controller.buildSavedAccountProfileSettings( settings: SettingsSnapshot.defaults(), accountBaseUrl: '', accountIdentifier: '', bridgeServerUrl: 'http://private-bridge.example.com:8787', bridgeToken: 'token', isManualBridge: true, ), throwsArgumentError, ); }); test('manual bridge save requires token authentication', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-manual-bridge-token-required-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); final controller = SettingsController(store); addTearDown(controller.dispose); await controller.initialize(); expect( controller.buildSavedAccountProfileSettings( settings: SettingsSnapshot.defaults(), accountBaseUrl: '', accountIdentifier: '', bridgeServerUrl: 'http://127.0.0.1:8787', bridgeToken: '', isManualBridge: true, ), throwsArgumentError, ); }); test( 'syncAccountSettings succeeds when bridge url metadata is missing', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-managed-bridge-missing-metadata-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( accountBaseUrl: 'https://accounts.svc.plus', accountUsername: 'review@svc.plus', ), ); await store.saveAccountSessionToken('session-token'); await store.saveAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, value: 'stale-bridge-token', ); final client = _FakeAccountRuntimeClient( loginPayload: const {}, sessionPayload: const { 'user': { 'id': 'user-1', 'email': 'review@svc.plus', }, }, syncPayload: const { 'BRIDGE_AUTH_TOKEN': 'fresh-bridge-token', }, ); final controller = SettingsController( store, accountClientFactory: (_) => client, ); addTearDown(controller.dispose); await controller.initialize(); final result = await controller.syncAccountSettings( baseUrl: 'https://accounts.svc.plus', ); expect(result.state, 'ready'); expect(result.message, 'Bridge access synced'); expect( await store.loadAccountManagedSecret( target: kAccountManagedSecretTargetBridgeAuthToken, ), 'fresh-bridge-token', ); expect(controller.accountSyncState!.syncedDefaults.bridgeServerUrl, ''); }, ); test( 'does not recover bridge sync state from stale cloud-synced snapshot state', () async { final storeRoot = await Directory.systemTemp.createTemp( 'xworkmate-account-recover-', ); addTearDown(() async { if (await storeRoot.exists()) { await storeRoot.delete(recursive: true); } }); final store = SecureConfigStore( secretRootPathResolver: () async => '${storeRoot.path}/secrets', appDataRootPathResolver: () async => '${storeRoot.path}/app-data', supportRootPathResolver: () async => '${storeRoot.path}/support', enableSecureStorage: false, ); await store.initialize(); await store.saveSettingsSnapshot( SettingsSnapshot.defaults().copyWith( acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults() .copyWith( cloudSynced: AcpBridgeServerModeConfig.defaults().cloudSynced .copyWith( lastSyncAt: DateTime( 2026, 4, 12, 11, ).millisecondsSinceEpoch, remoteServerSummary: AcpBridgeServerModeConfig.defaults() .cloudSynced .remoteServerSummary .copyWith(endpoint: 'https://bridge.svc.plus'), ), ), ), ); await store.saveSecretValueByRef( kAccountManagedSecretTargetBridgeAuthToken, 'bridge-token', ); final controller = SettingsController(store); addTearDown(controller.dispose); await controller.initialize(); expect(controller.accountSyncState, isNull); final persisted = await store.loadAccountSyncState(); expect(persisted, isNull); }, ); }); } class _FakeAccountRuntimeClient extends AccountRuntimeClient { _FakeAccountRuntimeClient({ required this.loginPayload, this.sessionPayload = const {}, this.syncPayload = const {}, }) : super(baseUrl: 'https://accounts.svc.plus'); final Map loginPayload; final Map sessionPayload; final Map syncPayload; int loadProfileCallCount = 0; int loadXWorkmateProfileSyncCallCount = 0; @override Future> login({ required String identifier, required String password, }) async { return loginPayload; } @override Future> loadProfile({required String token}) async { loadProfileCallCount += 1; return sessionPayload; } @override Future> loadXWorkmateProfileSync({ required String token, }) async { loadXWorkmateProfileSyncCallCount += 1; return syncPayload; } }