Files
fin-tracker/app/test/cache_interceptor_test.dart
T
Dmitry a559d6de3e feat(app): смена сервера API и очистка данных устройства при выходе
Адрес бэкенда хранится в shared_preferences и читается до runApp; switchApiBaseUrl сбрасывает токены и кэш старого сервера. Выход стирает refresh-токен и sqlite-кэш ответов. Экран настроек переделан под разделы.
2026-09-19 22:14:14 +03:00

135 lines
4.3 KiB
Dart

import 'package:dio/dio.dart';
import 'package:drift/native.dart';
import 'package:fintracker_app/core/cache/cache_interceptor.dart';
import 'package:fintracker_app/core/cache/response_cache.dart';
import 'package:flutter_test/flutter_test.dart';
/// `onResponse`/`onError` complete synchronously in `CacheInterceptor` — no
/// real Dio transport needed, same approach as `date_query_interceptor_test.dart`.
class _ResponseCapture extends ResponseInterceptorHandler {
Response<dynamic>? passed;
@override
void next(Response response) => passed = response;
}
class _ErrorCapture extends ErrorInterceptorHandler {
Response<dynamic>? resolvedResponse;
DioException? forwarded;
@override
void resolve(Response response) => resolvedResponse = response;
@override
void next(DioException error) => forwarded = error;
}
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,
);
void main() {
late ResponseCacheDatabase db;
late CacheInterceptor interceptor;
setUp(() {
db = ResponseCacheDatabase.forTesting(NativeDatabase.memory());
interceptor = CacheInterceptor(db);
});
tearDown(() => db.close());
test('a successful GET is written to the cache', () async {
final options = _request('GET', '/networth/breakdown');
interceptor.onResponse(
Response<dynamic>(
requestOptions: options,
statusCode: 200,
data: {'totalRub': '100'},
),
_ResponseCapture(),
);
await pumpEventQueue(); // the write is fire-and-forget
final cached = await db.get(CacheInterceptor.requestKey(options));
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'});
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);
},
);
test('a connection error with nothing cached still fails', () async {
final options = _request('GET', '/networth/breakdown', {'never': 'asked'});
final failure = _offline(options);
final handler = _ErrorCapture();
await interceptor.onError(failure, handler);
expect(handler.resolvedResponse, isNull);
expect(handler.forwarded, same(failure));
});
test('a real server error is never masked by a cached answer', () async {
final options = _request('GET', '/networth/breakdown');
await db.put(CacheInterceptor.requestKey(options), {'totalRub': '100'});
final serverError = DioException(
requestOptions: options,
type: DioExceptionType.badResponse,
response: Response<dynamic>(requestOptions: options, statusCode: 500),
);
final handler = _ErrorCapture();
await interceptor.onError(serverError, handler);
expect(handler.resolvedResponse, isNull);
expect(handler.forwarded, same(serverError));
});
test('a failed mutation is never served from the cache', () async {
final options = _request('POST', '/links');
final failure = _offline(options);
final handler = _ErrorCapture();
await interceptor.onError(failure, handler);
expect(handler.resolvedResponse, isNull);
expect(handler.forwarded, same(failure));
});
test('query parameters are part of the cache key', () async {
final page1 = _request('GET', '/events', {'page': 1});
final page2 = _request('GET', '/events', {'page': 2});
await db.put(CacheInterceptor.requestKey(page1), 'page1');
final handler = _ErrorCapture();
await interceptor.onError(_offline(page2), handler);
expect(handler.resolvedResponse, isNull); // page=2 was never cached
});
test('the key ignores query parameter order', () {
final a = _request('GET', '/events', {'a': 1, 'b': 2});
final b = _request('GET', '/events', {'b': 2, 'a': 1});
expect(CacheInterceptor.requestKey(a), CacheInterceptor.requestKey(b));
});
}