fix(runtime): harden application service boundaries

This commit is contained in:
edde746
2026-07-24 03:46:46 +02:00
parent 658da37b48
commit e0bf66eea8
309 changed files with 32574 additions and 4369 deletions
+91 -1
View File
@@ -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);
});
});
}