Fix managed bridge auth sync and offline connection state
This commit is contained in:
parent
29db450352
commit
05a346e372
@ -55,14 +55,20 @@ AssistantThreadConnectionState resolveGatewayThreadConnectionStateInternal({
|
||||
final bridgeAddress = connection.remoteAddress?.trim() ?? '';
|
||||
final rawStatus = connection.status;
|
||||
final gatewayTokenMissing = connection.gatewayTokenMissing;
|
||||
final missingEndpoint =
|
||||
(connection.lastErrorCode?.trim().toUpperCase() ?? '') ==
|
||||
'MISSING_ENDPOINT';
|
||||
final hasFailureEvidence =
|
||||
rawStatus == RuntimeConnectionStatus.error ||
|
||||
(connection.lastError?.trim().isNotEmpty ?? false) ||
|
||||
(connection.lastErrorCode?.trim().isNotEmpty ?? false) ||
|
||||
(connection.lastErrorDetailCode?.trim().isNotEmpty ?? false);
|
||||
!missingEndpoint &&
|
||||
(rawStatus == RuntimeConnectionStatus.error ||
|
||||
(connection.lastError?.trim().isNotEmpty ?? false) ||
|
||||
(connection.lastErrorCode?.trim().isNotEmpty ?? false) ||
|
||||
(connection.lastErrorDetailCode?.trim().isNotEmpty ?? false));
|
||||
final genericFailure = !gatewayTokenMissing && hasFailureEvidence;
|
||||
final status = gatewayTokenMissing || genericFailure
|
||||
? RuntimeConnectionStatus.error
|
||||
: missingEndpoint
|
||||
? RuntimeConnectionStatus.offline
|
||||
: rawStatus;
|
||||
final primaryLabel = gatewayTokenMissing
|
||||
? appText('缺少令牌', 'Missing Token')
|
||||
|
||||
@ -104,7 +104,8 @@ class GatewayRuntime extends ChangeNotifier with GatewayRuntimeHelpersInternal {
|
||||
bool get usesSessionClient => sessionClientInternal != null;
|
||||
|
||||
@visibleForTesting
|
||||
GatewayRuntimeSessionClient? get sessionClientForTest => sessionClientInternal;
|
||||
GatewayRuntimeSessionClient? get sessionClientForTest =>
|
||||
sessionClientInternal;
|
||||
|
||||
Future<void> initialize() async {
|
||||
sessionUpdatesInternal ??= sessionClientInternal?.updates.listen(
|
||||
@ -234,9 +235,6 @@ class GatewayRuntime extends ChangeNotifier with GatewayRuntimeHelpersInternal {
|
||||
);
|
||||
snapshotInternal = GatewayConnectionSnapshot.initial(mode: profile.mode)
|
||||
.copyWith(
|
||||
statusText: 'Missing gateway endpoint',
|
||||
lastError: 'Configure setup code or manual host / port first.',
|
||||
lastErrorCode: 'MISSING_ENDPOINT',
|
||||
deviceId: identity.deviceId,
|
||||
connectAuthMode: connectAuthMode,
|
||||
connectAuthFields: connectAuthFields,
|
||||
|
||||
@ -505,6 +505,12 @@ String _resolveBridgeAuthorizationToken(Map<String, dynamic> payload) {
|
||||
if (explicit.isNotEmpty) {
|
||||
return explicit;
|
||||
}
|
||||
final uppercaseInternalServiceToken = _stringValue(
|
||||
payload['INTERNAL_SERVICE_TOKEN'],
|
||||
);
|
||||
if (uppercaseInternalServiceToken.isNotEmpty) {
|
||||
return uppercaseInternalServiceToken;
|
||||
}
|
||||
final internalServiceToken = _stringValue(payload['internalServiceToken']);
|
||||
if (internalServiceToken.isNotEmpty) {
|
||||
return internalServiceToken;
|
||||
|
||||
@ -110,6 +110,45 @@ void main() {
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'treats missing endpoint as offline and hides stale english setup copy',
|
||||
(tester) async {
|
||||
final controller = AppController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
controller.runtimeInternal.snapshotInternal = controller
|
||||
.runtimeInternal
|
||||
.snapshot
|
||||
.copyWith(
|
||||
status: RuntimeConnectionStatus.error,
|
||||
statusText: 'Missing gateway endpoint',
|
||||
lastError: 'Configure setup code or manual host / port first.',
|
||||
lastErrorCode: 'MISSING_ENDPOINT',
|
||||
clearRemoteAddress: true,
|
||||
);
|
||||
|
||||
await tester.binding.setSurfaceSize(const Size(1440, 960));
|
||||
addTearDown(() async {
|
||||
await tester.binding.setSurfaceSize(null);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(_buildAssistantPage(controller));
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(find.text('Bridge 连接失败'), findsNothing);
|
||||
expect(find.text('先连接 Bridge'), findsOneWidget);
|
||||
expect(
|
||||
find.text('Configure setup code or manual host / port first.'),
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.text('当前 xworkmate-bridge 尚未连接。请先恢复 bridge 连接,再继续当前任务。'),
|
||||
findsOneWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -111,6 +111,35 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'treats missing endpoint as true offline instead of bridge failure',
|
||||
() async {
|
||||
final controller = AppController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
|
||||
controller.runtimeInternal.snapshotInternal = controller
|
||||
.runtimeInternal
|
||||
.snapshot
|
||||
.copyWith(
|
||||
status: RuntimeConnectionStatus.error,
|
||||
statusText: 'Missing gateway endpoint',
|
||||
lastError: 'Configure setup code or manual host / port first.',
|
||||
lastErrorCode: 'MISSING_ENDPOINT',
|
||||
clearRemoteAddress: true,
|
||||
);
|
||||
|
||||
final state = controller.currentAssistantConnectionState;
|
||||
expect(state.status, RuntimeConnectionStatus.offline);
|
||||
expect(state.primaryLabel, '离线');
|
||||
expect(state.detailLabel, 'xworkmate-bridge 未连接');
|
||||
},
|
||||
);
|
||||
|
||||
test('desktop snapshot uses derived assistant connection labels', () async {
|
||||
final controller = AppController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.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';
|
||||
@ -58,6 +59,64 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'login sync accepts INTERNAL_SERVICE_TOKEN payload for managed bridge auth',
|
||||
() 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: <String, dynamic>{
|
||||
'token': 'session-token',
|
||||
'INTERNAL_SERVICE_TOKEN': 'bridge-token-from-login',
|
||||
'user': <String, dynamic>{
|
||||
'id': 'user-1',
|
||||
'email': 'review@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(
|
||||
await store.loadAccountManagedSecret(
|
||||
target: kAccountManagedSecretTargetBridgeAuthToken,
|
||||
),
|
||||
'bridge-token-from-login',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('syncAccountSettings pins the managed bridge cloud entry', () async {
|
||||
final storeRoot = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-account-managed-bridge-',
|
||||
@ -179,3 +238,18 @@ void main() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class _FakeAccountRuntimeClient extends AccountRuntimeClient {
|
||||
_FakeAccountRuntimeClient({required this.loginPayload})
|
||||
: super(baseUrl: 'https://accounts.svc.plus');
|
||||
|
||||
final Map<String, dynamic> loginPayload;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> login({
|
||||
required String identifier,
|
||||
required String password,
|
||||
}) async {
|
||||
return loginPayload;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user