fix(media): parse responses before caching
This commit is contained in:
@@ -750,10 +750,11 @@ mixin MediaServerCacheMixin implements MediaServerClient {
|
||||
try {
|
||||
final response = await networkCall();
|
||||
throwIfHttpError(response);
|
||||
final parsed = parseResponse(response);
|
||||
if (cacheResponse) {
|
||||
await _putCacheResponse(cacheScope, cacheKey, response.data);
|
||||
}
|
||||
return parseResponse(response);
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
if (shouldFallback != null && !shouldFallback(e)) rethrow;
|
||||
appLogger.w('Network request failed for $cacheKey, trying cache', error: e);
|
||||
@@ -784,10 +785,11 @@ mixin MediaServerCacheMixin implements MediaServerClient {
|
||||
if (isOfflineMode) return null;
|
||||
final response = await networkCall();
|
||||
throwIfHttpError(response);
|
||||
final parsed = parseResponse(response);
|
||||
if (cacheResponse) {
|
||||
await _putCacheResponse(cacheScope, cacheKey, response.data);
|
||||
}
|
||||
return parseResponse(response);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
Future<void> _putCacheResponse(ServerId cacheScope, String cacheKey, dynamic data) async {
|
||||
|
||||
@@ -111,6 +111,25 @@ void main() {
|
||||
expect(await cache.get(client.serverId, '/metadata/no-cache'), isNull);
|
||||
});
|
||||
|
||||
test('cache-first leaves a rejected response uncached', () async {
|
||||
const key = '/metadata/rejected-response';
|
||||
const body = {'value': 'invalid'};
|
||||
final parseError = FormatException('invalid metadata');
|
||||
|
||||
await expectLater(
|
||||
client.fetchWithCacheFirst<String>(
|
||||
cacheScope: client.serverId,
|
||||
cacheKey: key,
|
||||
networkCall: () async => MediaServerResponse(statusCode: 200, data: body, headers: const {}),
|
||||
parseCache: (_) => 'cached',
|
||||
parseResponse: (_) => throw parseError,
|
||||
),
|
||||
throwsA(same(parseError)),
|
||||
);
|
||||
|
||||
expect(await cache.get(client.serverId, key), isNull);
|
||||
});
|
||||
|
||||
test('successful miss is parsed and cached exactly once', () async {
|
||||
var networkCalls = 0;
|
||||
var parserCalls = 0;
|
||||
@@ -214,6 +233,27 @@ void main() {
|
||||
expect(responseParserCalls, 0);
|
||||
});
|
||||
|
||||
test('omitted selector serves the previous cache after response parsing fails', () async {
|
||||
const key = '/metadata/parser-fallback';
|
||||
await cache.put(client.serverId, key, cachedBody);
|
||||
var cacheParserCalls = 0;
|
||||
|
||||
final value = await client.fetchWithCacheFallback<String>(
|
||||
cacheKey: key,
|
||||
networkCall: () async =>
|
||||
MediaServerResponse(statusCode: 200, data: const {'value': 'invalid'}, headers: const {}),
|
||||
parseCache: (cached) {
|
||||
cacheParserCalls++;
|
||||
return (cached as Map<String, dynamic>)['value'] as String;
|
||||
},
|
||||
parseResponse: (_) => throw const FormatException('invalid metadata'),
|
||||
);
|
||||
|
||||
expect(value, 'cached');
|
||||
expect(cacheParserCalls, 1);
|
||||
expect(await cache.get(client.serverId, key), cachedBody);
|
||||
});
|
||||
|
||||
test('rejecting selector rethrows an HTTP status without reading cached data', () async {
|
||||
const key = '/metadata/rejected-fallback';
|
||||
await cache.put(client.serverId, key, cachedBody);
|
||||
|
||||
@@ -219,7 +219,7 @@ void main() {
|
||||
|
||||
test('${api.name} propagates a valid JSON payload rejected by the item DTO', () async {
|
||||
final id = '${api.name}-cached-unmappable';
|
||||
await seedItem(id);
|
||||
final seededResponse = await seedItem(id);
|
||||
var requestCount = 0;
|
||||
final client = makeClient((_) async {
|
||||
requestCount++;
|
||||
@@ -236,6 +236,13 @@ void main() {
|
||||
await expectLater(lookup(client, id, includeOnDeck: api.includeOnDeck), throwsA(isA<TypeError>()));
|
||||
|
||||
expect(requestCount, 1);
|
||||
expect(await PlexApiCache.instance.get(defaultProfileScopeId.cacheServerId, endpointFor(id)), seededResponse);
|
||||
|
||||
client.setOfflineMode(true);
|
||||
final offlineResult = await lookup(client, id, includeOnDeck: api.includeOnDeck);
|
||||
expect(offlineResult.item?.id, id);
|
||||
expect(offlineResult.item?.title, 'Cached item');
|
||||
expect(requestCount, 1);
|
||||
});
|
||||
|
||||
test('${api.name} uses profile-scoped cache without HTTP while explicitly offline', () async {
|
||||
|
||||
Reference in New Issue
Block a user