refactor: type server identifiers

This commit is contained in:
edde746
2026-06-01 11:06:03 +02:00
parent 7b59dac26b
commit 74b8dc4561
258 changed files with 2296 additions and 2080 deletions
+43 -38
View File
@@ -1,4 +1,5 @@
import 'dart:io';
import 'package:plezy/media/ids.dart';
import 'package:drift/drift.dart' hide isNull, isNotNull;
import 'package:drift/native.dart';
@@ -436,7 +437,7 @@ class _AppDatabaseTestSuite {
group('OfflineWatchProgress', () {
test('upsertProgressAction inserts a new progress row', () async {
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '42',
viewOffset: 5000,
duration: 10000,
@@ -456,14 +457,14 @@ class _AppDatabaseTestSuite {
test('upsertProgressAction merges into the existing progress row', () async {
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '42',
viewOffset: 1000,
duration: 10000,
shouldMarkWatched: false,
);
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '42',
viewOffset: 9500,
duration: 10000,
@@ -478,7 +479,7 @@ class _AppDatabaseTestSuite {
test('upsertProgressAction keeps scoped Jellyfin users separate', () async {
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
clientScopeId: 'srv/user-a',
ratingKey: '42',
viewOffset: 1000,
@@ -486,7 +487,7 @@ class _AppDatabaseTestSuite {
shouldMarkWatched: false,
);
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
clientScopeId: 'srv/user-b',
ratingKey: '42',
viewOffset: 9000,
@@ -505,14 +506,18 @@ class _AppDatabaseTestSuite {
test('insertWatchAction (watched) clears prior progress + insert single row', () async {
// Existing progress row for the same item
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '42',
viewOffset: 5000,
duration: 10000,
shouldMarkWatched: false,
);
await db.insertWatchAction(serverId: 'srv', ratingKey: '42', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(
serverId: ServerId('srv'),
ratingKey: '42',
actionType: OfflineActionType.watched.id,
);
final rows = await db.select(db.offlineWatchProgress).get();
expect(rows, hasLength(1));
@@ -522,7 +527,7 @@ class _AppDatabaseTestSuite {
test('insertWatchAction clears only matching clientScopeId conflicts', () async {
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
clientScopeId: 'srv/user-a',
ratingKey: '42',
viewOffset: 1000,
@@ -530,7 +535,7 @@ class _AppDatabaseTestSuite {
shouldMarkWatched: false,
);
await db.upsertProgressAction(
serverId: 'srv',
serverId: ServerId('srv'),
clientScopeId: 'srv/user-b',
ratingKey: '42',
viewOffset: 2000,
@@ -539,7 +544,7 @@ class _AppDatabaseTestSuite {
);
await db.insertWatchAction(
serverId: 'srv',
serverId: ServerId('srv'),
clientScopeId: 'srv/user-a',
ratingKey: '42',
actionType: OfflineActionType.watched.id,
@@ -588,10 +593,10 @@ class _AppDatabaseTestSuite {
});
test('adoptLegacyOfflineWatchActionsForProfile claims null-profile rows', () async {
await db.insertWatchAction(serverId: 's', ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(
profileId: 'profile-existing',
serverId: 's',
serverId: ServerId('s'),
ratingKey: '2',
actionType: OfflineActionType.watched.id,
);
@@ -603,14 +608,14 @@ class _AppDatabaseTestSuite {
});
test('getPendingWatchActionsForServer filters by serverId', () async {
await db.insertWatchAction(serverId: 'a', ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: 'b', ratingKey: '2', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: 'a', ratingKey: '3', actionType: OfflineActionType.unwatched.id);
await db.insertWatchAction(serverId: ServerId('a'), ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('b'), ratingKey: '2', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('a'), ratingKey: '3', actionType: OfflineActionType.unwatched.id);
final aRows = await db.getPendingWatchActionsForServer('a');
final aRows = await db.getPendingWatchActionsForServer(ServerId('a'));
expect(aRows.map((r) => r.ratingKey).toSet(), {'1', '3'});
final bRows = await db.getPendingWatchActionsForServer('b');
final bRows = await db.getPendingWatchActionsForServer(ServerId('b'));
expect(bRows.map((r) => r.ratingKey).toSet(), {'2'});
});
@@ -774,7 +779,7 @@ class _AppDatabaseTestSuite {
});
test('updateSyncAttempt increments syncAttempts and stores lastError', () async {
await db.insertWatchAction(serverId: 's', ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '1', actionType: OfflineActionType.watched.id);
final inserted = (await db.select(db.offlineWatchProgress).get()).single;
await db.updateSyncAttempt(inserted.id, 'boom');
@@ -794,8 +799,8 @@ class _AppDatabaseTestSuite {
});
test('deleteWatchAction removes only the matching row', () async {
await db.insertWatchAction(serverId: 's', ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: 's', ratingKey: '2', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '2', actionType: OfflineActionType.watched.id);
final rows = await db.select(db.offlineWatchProgress).get();
expect(rows, hasLength(2));
@@ -806,14 +811,14 @@ class _AppDatabaseTestSuite {
test('getPendingSyncCount counts every row', () async {
expect(await db.getPendingSyncCount(), 0);
await db.insertWatchAction(serverId: 's', ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: 's', ratingKey: '2', actionType: OfflineActionType.unwatched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '2', actionType: OfflineActionType.unwatched.id);
expect(await db.getPendingSyncCount(), 2);
});
test('clearAllWatchActions empties the table', () async {
await db.insertWatchAction(serverId: 's', ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: 's', ratingKey: '2', actionType: OfflineActionType.unwatched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '1', actionType: OfflineActionType.watched.id);
await db.insertWatchAction(serverId: ServerId('s'), ratingKey: '2', actionType: OfflineActionType.unwatched.id);
await db.clearAllWatchActions();
expect(await db.select(db.offlineWatchProgress).get(), isEmpty);
@@ -829,7 +834,7 @@ class _AppDatabaseTestSuite {
group('SyncRules', () {
test('insertSyncRule + getSyncRules round-trip with defaults', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -854,14 +859,14 @@ class _AppDatabaseTestSuite {
// [globalKey] so re-creating a rule for the same target updates the
// existing row rather than throwing.
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
episodeCount: 5,
);
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'season',
@@ -879,7 +884,7 @@ class _AppDatabaseTestSuite {
test('insertSyncRule allows the same server item for different profiles', () async {
await db.insertSyncRule(
profileId: 'profile-a',
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'profile-a|srv:10',
targetType: 'show',
@@ -887,7 +892,7 @@ class _AppDatabaseTestSuite {
);
await db.insertSyncRule(
profileId: 'profile-b',
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'profile-b|srv:10',
targetType: 'show',
@@ -902,7 +907,7 @@ class _AppDatabaseTestSuite {
test('insertSyncRule preserves enabled + lastExecutedAt across upserts', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -913,7 +918,7 @@ class _AppDatabaseTestSuite {
final firstRun = (await db.getSyncRule('srv:10'))!;
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -927,7 +932,7 @@ class _AppDatabaseTestSuite {
test('getSyncRule returns the matching rule or null', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -939,7 +944,7 @@ class _AppDatabaseTestSuite {
test('updateSyncRuleCount mutates only the count', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -954,7 +959,7 @@ class _AppDatabaseTestSuite {
test('updateSyncRuleFilter mutates the filter', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -968,7 +973,7 @@ class _AppDatabaseTestSuite {
test('updateSyncRuleEnabled toggles enabled', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -983,7 +988,7 @@ class _AppDatabaseTestSuite {
test('updateSyncRuleLastExecuted writes a timestamp', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
@@ -1001,14 +1006,14 @@ class _AppDatabaseTestSuite {
test('deleteSyncRule removes the matching row', () async {
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '10',
globalKey: 'srv:10',
targetType: 'show',
episodeCount: 5,
);
await db.insertSyncRule(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '11',
globalKey: 'srv:11',
targetType: 'show',
+29 -28
View File
@@ -1,4 +1,5 @@
import 'package:drift/drift.dart' hide isNull, isNotNull;
import 'package:plezy/media/ids.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/database/app_database.dart';
@@ -23,7 +24,7 @@ void main() {
group('insertDownload', () {
test('inserts a movie row with defaults', () async {
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '100',
globalKey: 'srv:100',
type: 'movie',
@@ -45,7 +46,7 @@ void main() {
test('inserts an episode with parent and grandparent keys', () async {
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: 'ep1',
globalKey: 'srv:ep1',
type: 'episode',
@@ -63,7 +64,7 @@ void main() {
test('insertDownload uses InsertMode.insertOrReplace (re-insert overwrites)', () async {
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '100',
globalKey: 'srv:100',
type: 'movie',
@@ -74,7 +75,7 @@ void main() {
// Re-insert with the same globalKey — should replace, resetting progress to default 0.
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '100',
globalKey: 'srv:100',
type: 'movie',
@@ -140,14 +141,14 @@ void main() {
test('getNextQueueItem only returns items whose media is queued', () async {
// Two items in queue; one's media is still queued, the other is downloading.
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '1',
globalKey: 'srv:1',
type: 'movie',
status: DownloadStatus.queued.index,
);
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '2',
globalKey: 'srv:2',
type: 'movie',
@@ -166,21 +167,21 @@ void main() {
test('getNextQueueItem orders by priority desc, then addedAt asc', () async {
// All have queued status
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '1',
globalKey: 'srv:1',
type: 'movie',
status: DownloadStatus.queued.index,
);
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '2',
globalKey: 'srv:2',
type: 'movie',
status: DownloadStatus.queued.index,
);
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '3',
globalKey: 'srv:3',
type: 'movie',
@@ -212,7 +213,7 @@ void main() {
group('update helpers', () {
Future<void> seed({String key = 'srv:100'}) async {
await db.insertDownload(
serverId: key.split(':').first,
serverId: ServerId(key.split(':').first),
ratingKey: key.split(':').last,
globalKey: key,
type: 'movie',
@@ -308,7 +309,7 @@ void main() {
group('lookup helpers', () {
Future<void> seedTree() async {
await db.insertDownload(
serverId: 'srvA',
serverId: ServerId('srvA'),
ratingKey: 'ep1',
globalKey: 'srvA:ep1',
type: 'episode',
@@ -317,7 +318,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'srvA',
serverId: ServerId('srvA'),
ratingKey: 'ep2',
globalKey: 'srvA:ep2',
type: 'episode',
@@ -326,7 +327,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'srvA',
serverId: ServerId('srvA'),
ratingKey: 'ep3',
globalKey: 'srvA:ep3',
type: 'episode',
@@ -335,7 +336,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'srvB',
serverId: ServerId('srvB'),
ratingKey: 'movie1',
globalKey: 'srvB:movie1',
type: 'movie',
@@ -367,7 +368,7 @@ void main() {
test('getEpisodesBySeason can filter by server and client scope', () async {
await db.insertDownload(
serverId: 'jf',
serverId: ServerId('jf'),
clientScopeId: 'jf/user-a',
ratingKey: 'ep-a',
globalKey: 'jf:ep-a',
@@ -377,7 +378,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'jf',
serverId: ServerId('jf'),
clientScopeId: 'jf/user-b',
ratingKey: 'ep-b',
globalKey: 'jf:ep-b',
@@ -387,7 +388,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'other',
serverId: ServerId('other'),
ratingKey: 'ep-other',
globalKey: 'other:ep-other',
type: 'episode',
@@ -396,7 +397,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'other',
serverId: ServerId('other'),
clientScopeId: 'other/user-a',
ratingKey: 'ep-other-scoped',
globalKey: 'other:ep-other-scoped',
@@ -406,8 +407,8 @@ void main() {
status: DownloadStatus.completed.index,
);
final userA = await db.getEpisodesBySeason('season1', serverId: 'jf', clientScopeId: 'jf/user-a');
final unscoped = await db.getEpisodesBySeason('season1', serverId: 'other', filterClientScope: true);
final userA = await db.getEpisodesBySeason('season1', serverId: ServerId('jf'), clientScopeId: 'jf/user-a');
final unscoped = await db.getEpisodesBySeason('season1', serverId: ServerId('other'), filterClientScope: true);
expect(userA.map((e) => e.ratingKey), ['ep-a']);
expect(unscoped.map((e) => e.ratingKey), ['ep-other']);
@@ -424,7 +425,7 @@ void main() {
test('getEpisodesByShow can filter by server and client scope', () async {
await db.insertDownload(
serverId: 'jf',
serverId: ServerId('jf'),
clientScopeId: 'jf/user-a',
ratingKey: 'ep-a',
globalKey: 'jf:ep-a',
@@ -434,7 +435,7 @@ void main() {
status: DownloadStatus.completed.index,
);
await db.insertDownload(
serverId: 'jf',
serverId: ServerId('jf'),
clientScopeId: 'jf/user-b',
ratingKey: 'ep-b',
globalKey: 'jf:ep-b',
@@ -444,7 +445,7 @@ void main() {
status: DownloadStatus.completed.index,
);
final userB = await db.getEpisodesByShow('show1', serverId: 'jf', clientScopeId: 'jf/user-b');
final userB = await db.getEpisodesByShow('show1', serverId: ServerId('jf'), clientScopeId: 'jf/user-b');
expect(userB.map((e) => e.ratingKey), ['ep-b']);
});
@@ -452,13 +453,13 @@ void main() {
test('getDownloadsByServerId filters by serverId', () async {
await seedTree();
final a = await db.getDownloadsByServerId('srvA');
final a = await db.getDownloadsByServerId(ServerId('srvA'));
expect(a.map((e) => e.ratingKey).toSet(), {'ep1', 'ep2', 'ep3'});
final b = await db.getDownloadsByServerId('srvB');
final b = await db.getDownloadsByServerId(ServerId('srvB'));
expect(b.map((e) => e.ratingKey).toSet(), {'movie1'});
expect(await db.getDownloadsByServerId('srvZ'), isEmpty);
expect(await db.getDownloadsByServerId(ServerId('srvZ')), isEmpty);
});
});
@@ -513,7 +514,7 @@ void main() {
group('deleteDownload', () {
test('removes the row from downloadedMedia AND its queue entry', () async {
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '100',
globalKey: 'srv:100',
type: 'movie',
@@ -521,7 +522,7 @@ void main() {
);
await db.addToQueue(mediaGlobalKey: 'srv:100');
await db.insertDownload(
serverId: 'srv',
serverId: ServerId('srv'),
ratingKey: '200',
globalKey: 'srv:200',
type: 'movie',