fix(runtime): harden application service boundaries
This commit is contained in:
@@ -9,6 +9,7 @@ import 'package:plezy/media/media_backend.dart';
|
||||
import 'package:plezy/services/api_cache.dart';
|
||||
import 'package:plezy/services/jellyfin_api_cache.dart';
|
||||
import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/utils/active_client_scope.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
@@ -33,12 +34,22 @@ void main() {
|
||||
String title = 'Item',
|
||||
Object? librarySectionID,
|
||||
String? librarySectionTitle,
|
||||
int? viewCount,
|
||||
int? viewOffset,
|
||||
int? lastViewedAt,
|
||||
}) => {
|
||||
'MediaContainer': {
|
||||
'librarySectionID': ?librarySectionID,
|
||||
'librarySectionTitle': ?librarySectionTitle,
|
||||
'Metadata': [
|
||||
{'ratingKey': ratingKey, 'title': title, 'type': 'movie'},
|
||||
{
|
||||
'ratingKey': ratingKey,
|
||||
'title': title,
|
||||
'type': 'movie',
|
||||
'viewCount': ?viewCount,
|
||||
'viewOffset': ?viewOffset,
|
||||
'lastViewedAt': ?lastViewedAt,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -115,6 +126,23 @@ void main() {
|
||||
expect(hit, equals(payload));
|
||||
});
|
||||
|
||||
test('transfer namespace maps cached metadata back to the public server identity', () async {
|
||||
final transferScope = buildPlexTransferScopeId(ServerId('srv'));
|
||||
await cache.put(
|
||||
transferScope.cacheServerId,
|
||||
'/library/metadata/1',
|
||||
mediaContainer(ratingKey: '1', title: 'Transferred'),
|
||||
);
|
||||
await cache.pinForOffline(transferScope.cacheServerId, '1');
|
||||
|
||||
final item = await cache.getMetadata(transferScope.cacheServerId, '1');
|
||||
final all = await cache.getAllPinnedMetadata(cacheServerIds: {transferScope.cacheServerId});
|
||||
|
||||
expect(item?.serverId, 'srv');
|
||||
expect(item?.globalKey, 'srv:1');
|
||||
expect(all.keys, {'srv:1'});
|
||||
});
|
||||
|
||||
test('put on existing key overwrites prior data (insertOnConflictUpdate)', () async {
|
||||
await cache.put(ServerId('srv'), '/library/metadata/1', {
|
||||
'MediaContainer': {
|
||||
@@ -399,5 +427,67 @@ void main() {
|
||||
expect(result.keys, contains('srv:good'));
|
||||
expect(result.keys, isNot(contains('srv:bad')));
|
||||
});
|
||||
test('profile-scoped rows stay isolated while projecting the public item identity', () async {
|
||||
final publicServerId = ServerId('plex-public');
|
||||
final scopeA = buildPlexProfileScopeId(serverId: publicServerId, profileId: 'profile-a').cacheServerId;
|
||||
final scopeB = buildPlexProfileScopeId(serverId: publicServerId, profileId: 'profile-b').cacheServerId;
|
||||
const ratingKey = '42';
|
||||
const endpoint = '/library/metadata/$ratingKey';
|
||||
|
||||
await cache.put(
|
||||
scopeA,
|
||||
endpoint,
|
||||
mediaContainer(
|
||||
ratingKey: ratingKey,
|
||||
title: 'Profile A',
|
||||
viewCount: 0,
|
||||
viewOffset: 12000,
|
||||
lastViewedAt: 1700000001,
|
||||
),
|
||||
);
|
||||
await cache.put(
|
||||
scopeB,
|
||||
endpoint,
|
||||
mediaContainer(ratingKey: ratingKey, title: 'Profile B', viewCount: 1, viewOffset: 0, lastViewedAt: 1700000002),
|
||||
);
|
||||
await cache.pinForOffline(scopeA, ratingKey);
|
||||
await cache.pinForOffline(scopeB, ratingKey);
|
||||
|
||||
final singleA = await cache.getMetadata(scopeA, ratingKey);
|
||||
final singleB = await cache.getMetadata(scopeB, ratingKey);
|
||||
expect(singleA, isNotNull);
|
||||
expect(singleA!.serverId, 'plex-public');
|
||||
expect(singleA.globalKey, 'plex-public:42');
|
||||
expect(singleA.isWatched, isFalse);
|
||||
expect(singleA.viewOffsetMs, 12000);
|
||||
expect(singleA.lastViewedAt, 1700000001);
|
||||
expect(singleB, isNotNull);
|
||||
expect(singleB!.serverId, 'plex-public');
|
||||
expect(singleB.globalKey, 'plex-public:42');
|
||||
expect(singleB.isWatched, isTrue);
|
||||
expect(singleB.viewOffsetMs, 0);
|
||||
expect(singleB.lastViewedAt, 1700000002);
|
||||
|
||||
final bulkA = await cache.getAllPinnedMetadata(cacheServerIds: {scopeA});
|
||||
final bulkB = await cache.getAllPinnedMetadata(cacheServerIds: {scopeB});
|
||||
expect(bulkA.keys, ['plex-public:42']);
|
||||
expect(bulkA['plex-public:42']!.title, 'Profile A');
|
||||
expect(bulkA['plex-public:42']!.viewOffsetMs, 12000);
|
||||
expect(bulkB.keys, ['plex-public:42']);
|
||||
expect(bulkB['plex-public:42']!.title, 'Profile B');
|
||||
expect(bulkB['plex-public:42']!.isWatched, isTrue);
|
||||
|
||||
await cache.clearVolatile();
|
||||
expect(await cache.getMetadata(scopeA, ratingKey), isNotNull);
|
||||
expect(await cache.getMetadata(scopeB, ratingKey), isNotNull);
|
||||
expect(await cache.isPinnedRatingKey(scopeA, ratingKey), isTrue);
|
||||
expect(await cache.isPinnedRatingKey(scopeB, ratingKey), isTrue);
|
||||
|
||||
await cache.unpinForOffline(scopeA, ratingKey);
|
||||
await cache.deleteForItem(scopeA, ratingKey);
|
||||
expect(await cache.getMetadata(scopeA, ratingKey), isNull);
|
||||
expect(await cache.getMetadata(scopeB, ratingKey), isNotNull);
|
||||
expect(await cache.isPinnedRatingKey(scopeB, ratingKey), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user