refactor(downloads): unify deletion and scoped cache handling

This commit is contained in:
edde746
2026-07-12 08:42:23 +02:00
parent 3d36ecf094
commit c84b8b4792
12 changed files with 1023 additions and 272 deletions
@@ -0,0 +1,133 @@
import 'dart:convert';
import 'package:drift/drift.dart' show Value;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/services/jellyfin_cache_resolver.dart';
void main() {
late AppDatabase db;
late JellyfinCacheResolver resolver;
setUp(() {
db = AppDatabase.forTesting(NativeDatabase.memory());
resolver = JellyfinCacheResolver(db);
});
tearDown(() => db.close());
Future<void> insertConnection(String machineId, String userId, {String? profileId}) async {
final connectionId = '$machineId/$userId';
await db
.into(db.connections)
.insert(
ConnectionsCompanion.insert(
id: connectionId,
kind: 'jellyfin',
displayName: userId,
configJson: jsonEncode({'serverMachineId': machineId, 'userId': userId}),
createdAt: DateTime.now().millisecondsSinceEpoch,
),
);
if (profileId != null) {
await db
.into(db.profileConnections)
.insert(
ProfileConnectionsCompanion.insert(
profileId: profileId,
connectionId: connectionId,
userIdentifier: userId,
),
);
}
}
Future<void> insertItem(String scopeId, String userId, String itemId, {bool pinned = false}) {
return db
.into(db.apiCache)
.insert(
ApiCacheCompanion.insert(
cacheKey: '$scopeId:/Users/$userId/Items/$itemId',
data: jsonEncode({'Id': itemId, 'Name': userId}),
pinned: Value(pinned),
),
);
}
test('bare server id resolves its compound cache row and connection', () async {
await insertConnection('server', 'user-a');
await insertItem('server/user-a', 'user-a', 'item-1');
final match = await resolver.findResolvedItem('server', 'item-1');
expect(match?.key.scopeId, 'server/user-a');
expect(match?.connection.id, 'server/user-a');
});
test('compound scope selects the same user cache row, connection, and profile binding', () async {
await insertConnection('server', 'user-a', profileId: 'profile-a');
await insertConnection('server', 'user-b', profileId: 'profile-b');
await insertItem('server/user-a', 'user-a', 'item-1');
await insertItem('server/user-b', 'user-b', 'item-1');
final match = await resolver.findResolvedItem('server/user-b', 'item-1');
expect(match?.key.userId, 'user-b');
expect(match?.connection.id, 'server/user-b');
});
test('rejects a cache row whose compound scope and user segment disagree', () async {
await insertConnection('server', 'user-a');
await insertConnection('server', 'user-b');
await insertItem('server/user-a', 'user-b', 'item-1');
expect(await resolver.findItem('server/user-a', 'item-1'), isNull);
});
test('returns no match when the exact user connection is absent', () async {
await insertConnection('server', 'user-a');
await insertItem('server/user-b', 'user-b', 'item-1');
expect(await resolver.findResolvedItem('server/user-b', 'item-1'), isNull);
});
test('rejects a connection bound to a different Jellyfin user', () async {
await insertConnection('server', 'user-a');
await db
.into(db.profileConnections)
.insert(
ProfileConnectionsCompanion.insert(
profileId: 'profile-b',
connectionId: 'server/user-a',
userIdentifier: 'user-b',
),
);
await insertItem('server/user-a', 'user-a', 'item-1');
expect(await resolver.findResolvedItem('server/user-a', 'item-1'), isNull);
});
test('treats wildcard characters in server and item ids literally', () async {
await insertConnection('server_%', 'user-a');
await insertConnection('server-xx', 'user-a');
await insertItem('server_%/user-a', 'user-a', 'item_%');
await insertItem('server-xx/user-a', 'user-a', 'item-zz');
final match = await resolver.findResolvedItem('server_%', 'item_%');
expect(match?.key.scopeId, 'server_%/user-a');
expect(match?.key.itemId, 'item_%');
});
test('resolves pinned rows against each exact same-server user connection', () async {
await insertConnection('server', 'user-a', profileId: 'profile-a');
await insertConnection('server', 'user-b', profileId: 'profile-b');
await insertItem('server/user-a', 'user-a', 'item-a', pinned: true);
await insertItem('server/user-b', 'user-b', 'item-b', pinned: true);
final matches = await resolver.findPinnedItems();
expect(matches.map((match) => match.connection.id).toSet(), {'server/user-a', 'server/user-b'});
});
}