fix(settings): update account panel and assistant connection state
This commit is contained in:
parent
ab0ecdd005
commit
a353f6866f
@ -1303,7 +1303,7 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
|
||||
.isNotEmpty) {
|
||||
return false;
|
||||
}
|
||||
final envToken = runtimeEnvironmentValueInternal('BRIDGE_AUTH_TOKEN');
|
||||
final envToken = _runtimeBridgeAuthEnvTokenInternal();
|
||||
return envToken != null && envToken.isNotEmpty;
|
||||
}
|
||||
|
||||
@ -1421,10 +1421,20 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
|
||||
return bridgeToken?.isNotEmpty == true ? bridgeToken : null;
|
||||
}
|
||||
|
||||
final envToken = runtimeEnvironmentValueInternal('BRIDGE_AUTH_TOKEN');
|
||||
final envToken = _runtimeBridgeAuthEnvTokenInternal();
|
||||
return envToken?.isNotEmpty == true ? envToken : null;
|
||||
}
|
||||
|
||||
String? _runtimeBridgeAuthEnvTokenInternal() {
|
||||
final aiWorkspaceToken = runtimeEnvironmentValueInternal(
|
||||
'AI_WORKSPACE_AUTH_TOKEN',
|
||||
);
|
||||
if (aiWorkspaceToken != null && aiWorkspaceToken.isNotEmpty) {
|
||||
return aiWorkspaceToken;
|
||||
}
|
||||
return runtimeEnvironmentValueInternal('BRIDGE_AUTH_TOKEN');
|
||||
}
|
||||
|
||||
int? gatewayProfileIndexMatchingEndpointInternal(Uri endpoint) {
|
||||
final normalizedHost = endpoint.host.trim().toLowerCase();
|
||||
final normalizedScheme = endpoint.scheme.trim().toLowerCase();
|
||||
|
||||
@ -24,6 +24,7 @@ class SettingsAccountPanel extends StatefulWidget {
|
||||
required this.onVerifyMfa,
|
||||
required this.onCancelMfa,
|
||||
required this.onSync,
|
||||
required this.onResetManualBridge,
|
||||
required this.onLogout,
|
||||
});
|
||||
|
||||
@ -46,6 +47,7 @@ class SettingsAccountPanel extends StatefulWidget {
|
||||
final Future<void> Function() onVerifyMfa;
|
||||
final Future<void> Function() onCancelMfa;
|
||||
final Future<void> Function() onSync;
|
||||
final Future<void> Function() onResetManualBridge;
|
||||
final Future<void> Function() onLogout;
|
||||
|
||||
@override
|
||||
@ -89,7 +91,11 @@ class _SettingsAccountPanelState extends State<SettingsAccountPanel>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.accountSignedIn && !widget.accountMfaRequired) {
|
||||
final isManualBridgeConfigured =
|
||||
widget.settings.acpBridgeServerModeConfig.effective.source == 'bridge';
|
||||
if (!widget.accountSignedIn &&
|
||||
!widget.accountMfaRequired &&
|
||||
!isManualBridgeConfigured) {
|
||||
return AnimatedBuilder(
|
||||
animation: _signedOutTabController,
|
||||
builder: (context, _) {
|
||||
@ -151,6 +157,7 @@ class _SettingsAccountPanelState extends State<SettingsAccountPanel>
|
||||
accountStatus: widget.accountStatus,
|
||||
onSaveAccountProfile: widget.onSaveAccountProfile,
|
||||
onSync: widget.onSync,
|
||||
onResetManualBridge: widget.onResetManualBridge,
|
||||
onLogout: widget.onLogout,
|
||||
);
|
||||
}
|
||||
@ -464,6 +471,7 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
required this.accountStatus,
|
||||
required this.onSaveAccountProfile,
|
||||
required this.onSync,
|
||||
required this.onResetManualBridge,
|
||||
required this.onLogout,
|
||||
});
|
||||
|
||||
@ -475,6 +483,7 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
final Future<void> Function({required bool isManualBridge})
|
||||
onSaveAccountProfile;
|
||||
final Future<void> Function() onSync;
|
||||
final Future<void> Function() onResetManualBridge;
|
||||
final Future<void> Function() onLogout;
|
||||
|
||||
@override
|
||||
@ -525,9 +534,8 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
final primaryActionKey = isAccountSyncMode
|
||||
? 'settings-account-sync-button'
|
||||
: 'settings-account-manual-reset-button';
|
||||
final primaryAction = isAccountSyncMode
|
||||
? onSync
|
||||
: () => onSaveAccountProfile(isManualBridge: true);
|
||||
final primaryAction = isAccountSyncMode ? onSync : onResetManualBridge;
|
||||
final exitAction = isAccountSyncMode ? onLogout : onResetManualBridge;
|
||||
final mfaEnabled =
|
||||
accountSession?.totpEnabled == true ||
|
||||
accountSession?.mfaEnabled == true;
|
||||
@ -630,7 +638,7 @@ class _SignedInAccountPanel extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
key: const ValueKey('settings-account-logout-button'),
|
||||
onPressed: accountBusy ? null : () => onLogout(),
|
||||
onPressed: accountBusy ? null : () => exitAction(),
|
||||
child: Text(appText('退出', 'Exit')),
|
||||
),
|
||||
],
|
||||
@ -763,6 +771,9 @@ _SignedInAccountMode _signedInAccountModeFromSettings({
|
||||
required SettingsSnapshot settings,
|
||||
required AccountSyncState? accountState,
|
||||
}) {
|
||||
if (settings.acpBridgeServerModeConfig.effective.source == 'bridge') {
|
||||
return _SignedInAccountMode.manualBridge;
|
||||
}
|
||||
if (accountState?.profileScope.trim().toLowerCase() == 'bridge') {
|
||||
return _SignedInAccountMode.accountSync;
|
||||
}
|
||||
|
||||
@ -324,6 +324,25 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
await _refreshAboutSnapshot();
|
||||
}
|
||||
|
||||
Future<void> _resetManualBridge() async {
|
||||
final current = widget.controller.settings;
|
||||
final passwordRef =
|
||||
current.acpBridgeServerModeConfig.selfHosted.passwordRef;
|
||||
if (passwordRef.trim().isNotEmpty) {
|
||||
await widget.controller.settingsController.storeInternal
|
||||
.clearSecretValueByRef(passwordRef);
|
||||
}
|
||||
final nextSettings = current.copyWith(
|
||||
acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults(),
|
||||
);
|
||||
await widget.controller.saveSettings(nextSettings, refreshAfterSave: false);
|
||||
_bridgeUrlController.clear();
|
||||
_bridgeTokenController.clear();
|
||||
_lastSavedBridgeUrl = '';
|
||||
await widget.controller.settingsController.refreshDerivedState();
|
||||
await _refreshAboutSnapshot();
|
||||
}
|
||||
|
||||
Future<void> _refreshAboutSnapshot() async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
@ -514,6 +533,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
_verifyAccountMfa(widget.controller.settings),
|
||||
onCancelMfa: _cancelAccountMfa,
|
||||
onSync: () => _syncAccount(widget.controller.settings),
|
||||
onResetManualBridge: _resetManualBridge,
|
||||
onLogout: _logoutAccount,
|
||||
),
|
||||
),
|
||||
|
||||
@ -596,6 +596,10 @@ String _extractBridgeAuthTokenMetadata(Map<String, dynamic> payload) {
|
||||
if (reviewToken.isNotEmpty) {
|
||||
return reviewToken;
|
||||
}
|
||||
final aiWorkspaceToken = _stringValue(payload['AI_WORKSPACE_AUTH_TOKEN']);
|
||||
if (aiWorkspaceToken.isNotEmpty) {
|
||||
return aiWorkspaceToken;
|
||||
}
|
||||
return _stringValue(payload['BRIDGE_AUTH_TOKEN']);
|
||||
}
|
||||
|
||||
@ -644,10 +648,25 @@ Future<SettingsSnapshot> buildSavedAccountProfileSettingsInternal(
|
||||
required bool isManualBridge,
|
||||
}) async {
|
||||
final bridgeConfig = settings.acpBridgeServerModeConfig;
|
||||
final trimmedBridgeServerUrl = bridgeServerUrl.trim();
|
||||
final trimmedBridgeToken = bridgeToken.trim();
|
||||
final existingBridgeToken = isManualBridge
|
||||
? ((await controller.storeInternal.loadSecretValueByRef(
|
||||
bridgeConfig.selfHosted.passwordRef,
|
||||
))?.trim() ??
|
||||
'')
|
||||
: '';
|
||||
if (isManualBridge) {
|
||||
_validateManualBridgeProfile(
|
||||
serverUrl: trimmedBridgeServerUrl,
|
||||
tokenConfigured:
|
||||
trimmedBridgeToken.isNotEmpty || existingBridgeToken.isNotEmpty,
|
||||
);
|
||||
}
|
||||
final nextBridgeConfig = bridgeConfig.copyWith(
|
||||
selfHosted: isManualBridge
|
||||
? bridgeConfig.selfHosted.copyWith(
|
||||
serverUrl: bridgeServerUrl.trim(),
|
||||
serverUrl: trimmedBridgeServerUrl,
|
||||
username: 'admin',
|
||||
)
|
||||
: bridgeConfig.selfHosted,
|
||||
@ -663,7 +682,6 @@ Future<SettingsSnapshot> buildSavedAccountProfileSettingsInternal(
|
||||
effective: nextEffective,
|
||||
),
|
||||
);
|
||||
final trimmedBridgeToken = bridgeToken.trim();
|
||||
if (isManualBridge && trimmedBridgeToken.isNotEmpty) {
|
||||
await controller.saveSecretValueByRef(
|
||||
nextSettings.acpBridgeServerModeConfig.selfHosted.passwordRef,
|
||||
@ -675,6 +693,53 @@ Future<SettingsSnapshot> buildSavedAccountProfileSettingsInternal(
|
||||
return nextSettings;
|
||||
}
|
||||
|
||||
void _validateManualBridgeProfile({
|
||||
required String serverUrl,
|
||||
required bool tokenConfigured,
|
||||
}) {
|
||||
if (serverUrl.isEmpty) {
|
||||
throw ArgumentError.value(
|
||||
serverUrl,
|
||||
'bridgeServerUrl',
|
||||
'Bridge URL is required',
|
||||
);
|
||||
}
|
||||
if (!tokenConfigured) {
|
||||
throw ArgumentError.value(
|
||||
'',
|
||||
'bridgeToken',
|
||||
'Bridge auth token is required',
|
||||
);
|
||||
}
|
||||
final uri = Uri.tryParse(serverUrl);
|
||||
if (uri == null || !uri.hasScheme || uri.host.trim().isEmpty) {
|
||||
throw ArgumentError.value(
|
||||
serverUrl,
|
||||
'bridgeServerUrl',
|
||||
'Bridge URL must be a valid URL',
|
||||
);
|
||||
}
|
||||
final scheme = uri.scheme.toLowerCase();
|
||||
final host = uri.host.toLowerCase();
|
||||
final isLocalHttp =
|
||||
scheme == 'http' &&
|
||||
(host == '127.0.0.1' || host == 'localhost') &&
|
||||
uri.hasPort &&
|
||||
uri.port >= 1 &&
|
||||
uri.port <= 65535;
|
||||
if (isLocalHttp) {
|
||||
return;
|
||||
}
|
||||
if (scheme == 'https') {
|
||||
return;
|
||||
}
|
||||
throw ArgumentError.value(
|
||||
serverUrl,
|
||||
'bridgeServerUrl',
|
||||
'Manual Bridge URL must be http://127.0.0.1:<port> or http://localhost:<port> for local mode, or https:// for public custom bridge mode',
|
||||
);
|
||||
}
|
||||
|
||||
int _parseExpiresAtMs(Object? value) {
|
||||
if (value is int) {
|
||||
return value;
|
||||
|
||||
@ -40,6 +40,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
@ -97,6 +98,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
@ -151,6 +153,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
@ -206,6 +209,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
@ -260,6 +264,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
@ -321,6 +326,7 @@ void main() {
|
||||
addTearDown(controllers.dispose);
|
||||
|
||||
var syncCount = 0;
|
||||
var resetCount = 0;
|
||||
var logoutCount = 0;
|
||||
|
||||
final settings = SettingsSnapshot.defaults().copyWith(
|
||||
@ -382,6 +388,9 @@ void main() {
|
||||
onSync: () async {
|
||||
syncCount += 1;
|
||||
},
|
||||
onResetManualBridge: () async {
|
||||
resetCount += 1;
|
||||
},
|
||||
onLogout: () async {
|
||||
logoutCount += 1;
|
||||
},
|
||||
@ -430,6 +439,7 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
expect(syncCount, 1);
|
||||
expect(resetCount, 0);
|
||||
expect(logoutCount, 1);
|
||||
},
|
||||
);
|
||||
@ -476,6 +486,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
@ -538,8 +549,8 @@ void main() {
|
||||
addTearDown(controllers.dispose);
|
||||
|
||||
var saveCount = 0;
|
||||
var resetCount = 0;
|
||||
var logoutCount = 0;
|
||||
var receivedManualBridge = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
@ -574,12 +585,14 @@ void main() {
|
||||
bridgeTokenController: controllers.bridgeToken,
|
||||
onSaveAccountProfile: ({required bool isManualBridge}) async {
|
||||
saveCount += 1;
|
||||
receivedManualBridge = isManualBridge;
|
||||
},
|
||||
onLogin: () async {},
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {
|
||||
resetCount += 1;
|
||||
},
|
||||
onLogout: () async {
|
||||
logoutCount += 1;
|
||||
},
|
||||
@ -607,9 +620,86 @@ void main() {
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(saveCount, 1);
|
||||
expect(receivedManualBridge, isTrue);
|
||||
expect(logoutCount, 1);
|
||||
expect(saveCount, 0);
|
||||
expect(resetCount, 2);
|
||||
expect(logoutCount, 0);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'shows manual bridge status when saved without account sign-in',
|
||||
(tester) async {
|
||||
final controllers = _TestControllers();
|
||||
addTearDown(controllers.dispose);
|
||||
|
||||
var saveCount = 0;
|
||||
var resetCount = 0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
child: SettingsAccountPanel(
|
||||
settings: SettingsSnapshot.defaults().copyWith(
|
||||
acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults()
|
||||
.copyWith(
|
||||
effective: const AcpBridgeServerEffectiveConfig(
|
||||
endpoint: 'http://127.0.0.1:8787',
|
||||
tokenRef: 'acp_bridge_server_password',
|
||||
source: 'bridge',
|
||||
reason:
|
||||
'Manual Bridge configuration is present and valid',
|
||||
),
|
||||
selfHosted: AcpBridgeServerModeConfig.defaults()
|
||||
.selfHosted
|
||||
.copyWith(
|
||||
serverUrl: 'http://127.0.0.1:8787',
|
||||
username: 'admin',
|
||||
),
|
||||
),
|
||||
),
|
||||
accountSession: null,
|
||||
accountState: null,
|
||||
accountBusy: false,
|
||||
accountSignedIn: false,
|
||||
accountMfaRequired: false,
|
||||
accountBaseUrlController: controllers.baseUrl,
|
||||
accountIdentifierController: controllers.identifier,
|
||||
accountPasswordController: controllers.password,
|
||||
accountMfaCodeController: controllers.mfaCode,
|
||||
bridgeUrlController: controllers.bridgeUrl,
|
||||
bridgeTokenController: controllers.bridgeToken,
|
||||
onSaveAccountProfile: ({required bool isManualBridge}) async {
|
||||
saveCount += 1;
|
||||
},
|
||||
onLogin: () async {},
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {
|
||||
resetCount += 1;
|
||||
},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('手动 Bridge'), findsOneWidget);
|
||||
expect(find.textContaining('保存状态'), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-account-manual-reset-button')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-manual-bridge-save-button')),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const ValueKey('settings-account-manual-reset-button')),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(saveCount, 0);
|
||||
expect(resetCount, 1);
|
||||
},
|
||||
);
|
||||
|
||||
@ -653,6 +743,7 @@ void main() {
|
||||
onVerifyMfa: () async {},
|
||||
onCancelMfa: () async {},
|
||||
onSync: () async {},
|
||||
onResetManualBridge: () async {},
|
||||
onLogout: () async {},
|
||||
),
|
||||
),
|
||||
|
||||
@ -72,7 +72,7 @@ void main() {
|
||||
AssistantExecutionTarget.gateway,
|
||||
],
|
||||
environmentOverride: const <String, String>{
|
||||
'BRIDGE_AUTH_TOKEN': 'bridge-token',
|
||||
'AI_WORKSPACE_AUTH_TOKEN': 'bridge-token',
|
||||
},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
@ -244,6 +244,7 @@ void main() {
|
||||
},
|
||||
},
|
||||
syncPayload: const <String, dynamic>{
|
||||
'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',
|
||||
},
|
||||
@ -284,16 +285,18 @@ void main() {
|
||||
await store.loadAccountManagedSecret(
|
||||
target: kAccountManagedSecretTargetBridgeAuthToken,
|
||||
),
|
||||
'bridge-token-from-sync',
|
||||
'ai-workspace-token-from-sync',
|
||||
);
|
||||
expect(
|
||||
controller.snapshot.toJsonString().contains('bridge-token-from-sync'),
|
||||
controller.snapshot.toJsonString().contains(
|
||||
'ai-workspace-token-from-sync',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
jsonEncode(
|
||||
controller.accountSyncState!.toJson(),
|
||||
).contains('bridge-token-from-sync'),
|
||||
).contains('ai-workspace-token-from-sync'),
|
||||
isFalse,
|
||||
);
|
||||
},
|
||||
@ -789,6 +792,221 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user