import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:jinzhi_flutter_app/common/storage/secure_identity_storage.dart'; import 'package:jinzhi_flutter_app/common/storage/storage_keys.dart'; import 'package:jinzhi_flutter_app/common/storage/preferences_storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { setUp(() => SharedPreferences.setMockInitialValues({})); test( 'migrates a legacy SharedPreferences token into secure storage', () async { SharedPreferences.setMockInitialValues({ storageLegacyAccessToken: ' legacy ', }); final preferences = await SharedPreferences.getInstance(); final secure = _MemorySecureStorage(); final storage = SecureIdentityStorage( PreferencesStorage(preferences), secureStorage: secure, ); expect(await storage.restoreAccessToken(), 'legacy'); expect(await secure.read('access_token'), 'legacy'); expect(preferences.containsKey(storageLegacyAccessToken), isFalse); expect(preferences.getBool(storageAuthSessionActive), isTrue); }, ); test( 'rejects an orphaned Keychain token when the install marker is absent', () async { final preferences = await SharedPreferences.getInstance(); final secure = _MemorySecureStorage({'jwt_token': 'secure-legacy'}); final storage = SecureIdentityStorage( PreferencesStorage(preferences), secureStorage: secure, ); expect(await storage.restoreAccessToken(), isNull); expect(await secure.read('access_token'), isNull); expect(await secure.read('jwt_token'), isNull); }, ); test('restores a secure token only with the installation marker', () async { SharedPreferences.setMockInitialValues({storageAuthSessionActive: true}); final preferences = await SharedPreferences.getInstance(); final secure = _MemorySecureStorage({secureAccessToken: 'active'}); final storage = SecureIdentityStorage( PreferencesStorage(preferences), secureStorage: secure, ); expect(await storage.restoreAccessToken(), 'active'); }); test('logout clears session secrets but preserves device identity', () async { SharedPreferences.setMockInitialValues({storageAuthSessionActive: true}); final preferences = await SharedPreferences.getInstance(); final secure = _MemorySecureStorage({ secureAccessToken: 'access', secureMallCredential: 'mall', secureRefreshToken: 'refresh', secureBiometricCredential: 'biometric', secureDeviceId: 'device', }); final storage = SecureIdentityStorage( PreferencesStorage(preferences), secureStorage: secure, ); await storage.clearSessionCredentials(); expect(await secure.read(secureAccessToken), isNull); expect(await secure.read(secureMallCredential), isNull); expect(await secure.read(secureRefreshToken), isNull); expect(await secure.read(secureBiometricCredential), 'biometric'); expect(await secure.read(secureDeviceId), 'device'); await storage.clearAccountCredentials(); expect(await secure.read(secureBiometricCredential), isNull); expect(await secure.read(secureDeviceId), 'device'); }); test( 'serializes restore with logout so an old token cannot revive', () async { SharedPreferences.setMockInitialValues({storageAuthSessionActive: true}); final preferences = await SharedPreferences.getInstance(); final secure = _BlockingReadSecureStorage({ secureAccessToken: 'old-token', }); final session = AuthSessionStorage( SecureIdentityStorage( PreferencesStorage(preferences), secureStorage: secure, ), ); final restoring = session.restore(); await secure.readStarted.future; final clearing = session.clear(); secure.releaseRead.complete(); expect(await restoring, 'old-token'); await clearing; expect(session.accessToken, isNull); expect(await secure.read(secureAccessToken), isNull); }, ); test('serializes restore with login so the new token wins', () async { SharedPreferences.setMockInitialValues({storageAuthSessionActive: true}); final preferences = await SharedPreferences.getInstance(); final secure = _BlockingReadSecureStorage({secureAccessToken: 'old-token'}); final session = AuthSessionStorage( SecureIdentityStorage( PreferencesStorage(preferences), secureStorage: secure, ), ); final restoring = session.restore(); await secure.readStarted.future; final saving = session.save('new-token'); secure.releaseRead.complete(); expect(await restoring, 'old-token'); await saving; expect(session.accessToken, 'new-token'); expect(await secure.read(secureAccessToken), 'new-token'); }); } class _MemorySecureStorage implements SecureKeyValueStorage { _MemorySecureStorage([Map? initial]) : _values = {...?initial}; final Map _values; @override Future delete(String key) async => _values.remove(key); @override Future read(String key) async => _values[key]; @override Future write(String key, String value) async { _values[key] = value; } } class _BlockingReadSecureStorage extends _MemorySecureStorage { _BlockingReadSecureStorage(super.initial); final readStarted = Completer(); final releaseRead = Completer(); var _blocked = false; @override Future read(String key) async { if (key == secureAccessToken && !_blocked) { _blocked = true; readStarted.complete(); await releaseRead.future; } return super.read(key); } }