feat(app): смена сервера API и очистка данных устройства при выходе

Адрес бэкенда хранится в shared_preferences и читается до runApp; switchApiBaseUrl сбрасывает токены и кэш старого сервера. Выход стирает refresh-токен и sqlite-кэш ответов. Экран настроек переделан под разделы.
This commit is contained in:
Dmitry
2026-09-19 22:14:14 +03:00
parent bfe74ca47c
commit a559d6de3e
17 changed files with 887 additions and 135 deletions
+26 -14
View File
@@ -23,11 +23,16 @@ class _ErrorCapture extends ErrorInterceptorHandler {
void next(DioException error) => forwarded = error;
}
RequestOptions _request(String method, String path, [Map<String, dynamic> query = const {}]) =>
RequestOptions(path: path, method: method, queryParameters: query);
RequestOptions _request(
String method,
String path, [
Map<String, dynamic> query = const {},
]) => RequestOptions(path: path, method: method, queryParameters: query);
DioException _offline(RequestOptions options) =>
DioException(requestOptions: options, type: DioExceptionType.connectionError);
DioException _offline(RequestOptions options) => DioException(
requestOptions: options,
type: DioExceptionType.connectionError,
);
void main() {
late ResponseCacheDatabase db;
@@ -44,7 +49,11 @@ void main() {
final options = _request('GET', '/networth/breakdown');
interceptor.onResponse(
Response<dynamic>(requestOptions: options, statusCode: 200, data: {'totalRub': '100'}),
Response<dynamic>(
requestOptions: options,
statusCode: 200,
data: {'totalRub': '100'},
),
_ResponseCapture(),
);
await pumpEventQueue(); // the write is fire-and-forget
@@ -53,17 +62,20 @@ void main() {
expect(cached?.body, {'totalRub': '100'});
});
test('a connection error is answered from the cache, with fetchedAt set', () async {
final options = _request('GET', '/networth/breakdown');
await db.put(CacheInterceptor.requestKey(options), {'totalRub': '100'});
test(
'a connection error is answered from the cache, with fetchedAt set',
() async {
final options = _request('GET', '/networth/breakdown');
await db.put(CacheInterceptor.requestKey(options), {'totalRub': '100'});
final handler = _ErrorCapture();
await interceptor.onError(_offline(options), handler);
final handler = _ErrorCapture();
await interceptor.onError(_offline(options), handler);
expect(handler.resolvedResponse?.data, {'totalRub': '100'});
expect(handler.resolvedResponse?.extra['fetchedAt'], isA<DateTime>());
expect(handler.forwarded, isNull);
});
expect(handler.resolvedResponse?.data, {'totalRub': '100'});
expect(handler.resolvedResponse?.extra['fetchedAt'], isA<DateTime>());
expect(handler.forwarded, isNull);
},
);
test('a connection error with nothing cached still fails', () async {
final options = _request('GET', '/networth/breakdown', {'never': 'asked'});