feat(app): смена сервера API и очистка данных устройства при выходе
Адрес бэкенда хранится в shared_preferences и читается до runApp; switchApiBaseUrl сбрасывает токены и кэш старого сервера. Выход стирает refresh-токен и sqlite-кэш ответов. Экран настроек переделан под разделы.
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:fintracker_app/core/auth/auth_controller.dart';
|
||||
import 'package:fintracker_app/core/auth/switch_server.dart';
|
||||
import 'package:fintracker_app/core/auth/token_store.dart';
|
||||
import 'package:fintracker_app/core/cache/response_cache.dart';
|
||||
import 'package:fintracker_app/core/cache/response_cache_provider.dart';
|
||||
import 'package:fintracker_app/core/config.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// No stored refresh token, so `logout()` never touches the network.
|
||||
class _NoTokenStore extends TokenStore {
|
||||
bool cleared = false;
|
||||
|
||||
@override
|
||||
Future<String?> readRefresh() async => null;
|
||||
|
||||
@override
|
||||
Future<void> clear() async => cleared = true;
|
||||
}
|
||||
|
||||
void main() {
|
||||
late ResponseCacheDatabase db;
|
||||
late _NoTokenStore tokens;
|
||||
|
||||
ProviderContainer container({String? storedUrl}) {
|
||||
final c = ProviderContainer(
|
||||
overrides: [
|
||||
responseCacheDbProvider.overrideWithValue(db),
|
||||
tokenStoreProvider.overrideWithValue(tokens),
|
||||
initialApiBaseUrlProvider.overrideWithValue(storedUrl),
|
||||
],
|
||||
);
|
||||
addTearDown(c.dispose);
|
||||
return c;
|
||||
}
|
||||
|
||||
setUp(() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
db = ResponseCacheDatabase.forTesting(NativeDatabase.memory());
|
||||
tokens = _NoTokenStore();
|
||||
await db.put('/networth/breakdown', {'totalRub': '100'});
|
||||
});
|
||||
|
||||
tearDown(() => db.close());
|
||||
|
||||
test('logout drops the token and every cached response', () async {
|
||||
final c = container();
|
||||
|
||||
await c.read(authControllerProvider.notifier).logout();
|
||||
|
||||
expect(tokens.cleared, isTrue);
|
||||
expect(await db.get('/networth/breakdown'), isNull);
|
||||
expect(c.read(authControllerProvider).status, AuthStatus.signedOut);
|
||||
});
|
||||
|
||||
group('API base URL', () {
|
||||
test('a saved address wins over the default', () {
|
||||
expect(
|
||||
container(storedUrl: 'https://fin.example.com')
|
||||
.read(apiBaseUrlProvider),
|
||||
'https://fin.example.com',
|
||||
);
|
||||
expect(container().read(apiBaseUrlProvider), defaultApiBaseUrl());
|
||||
});
|
||||
|
||||
test('normalize accepts http(s) origins and trims the trailing slash', () {
|
||||
expect(
|
||||
ApiBaseUrlController.normalize(' https://fin.example.com/ '),
|
||||
'https://fin.example.com',
|
||||
);
|
||||
expect(
|
||||
ApiBaseUrlController.normalize('http://10.0.0.5:8000'),
|
||||
'http://10.0.0.5:8000',
|
||||
);
|
||||
expect(ApiBaseUrlController.normalize('fin.example.com'), isNull);
|
||||
expect(ApiBaseUrlController.normalize('ftp://fin.example.com'), isNull);
|
||||
expect(ApiBaseUrlController.normalize(''), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'switching servers signs out, clears the cache and persists the address',
|
||||
() async {
|
||||
final c = container();
|
||||
|
||||
await switchApiBaseUrl(c, 'https://other.example.com');
|
||||
|
||||
expect(c.read(apiBaseUrlProvider), 'https://other.example.com');
|
||||
expect(await loadStoredApiBaseUrl(), 'https://other.example.com');
|
||||
expect(tokens.cleared, isTrue);
|
||||
expect(await db.get('/networth/breakdown'), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'saving the address already in use keeps the session and the cache',
|
||||
() async {
|
||||
final c = container(storedUrl: 'https://fin.example.com');
|
||||
|
||||
await switchApiBaseUrl(c, 'https://fin.example.com');
|
||||
|
||||
expect(tokens.cleared, isFalse);
|
||||
expect(await db.get('/networth/breakdown'), isNotNull);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'null goes back to the default and forgets the saved address',
|
||||
() async {
|
||||
final c = container(storedUrl: 'https://fin.example.com');
|
||||
|
||||
await switchApiBaseUrl(c, null);
|
||||
|
||||
expect(c.read(apiBaseUrlProvider), defaultApiBaseUrl());
|
||||
expect(await loadStoredApiBaseUrl(), isNull);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user