refactor(features): consolidate shared feature primitives
This commit is contained in:
@@ -10,6 +10,7 @@ import 'package:plezy/services/catalog/catalog_source.dart';
|
||||
import 'package:plezy/services/catalog/trakt_catalog_source.dart';
|
||||
import 'package:plezy/services/trackers/tracker_session.dart';
|
||||
import 'package:plezy/services/trakt/trakt_client.dart';
|
||||
import '../../test_helpers/media_items.dart';
|
||||
|
||||
TrackerSession _session() {
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
@@ -105,7 +106,7 @@ void main() {
|
||||
expect(show.rating, 8.5);
|
||||
expect(page.items[0].airStatus, isNull);
|
||||
|
||||
final rendered = page.items[0].toMediaItem();
|
||||
final rendered = page.items[0].totestMediaItem();
|
||||
expect(rendered.serverId, isNull);
|
||||
expect(rendered.title, 'The Matrix');
|
||||
expect(rendered.isCatalogItem, isTrue);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/models/companion_remote/remote_command.dart';
|
||||
import 'package:plezy/services/companion_remote/companion_remote_peer_service.dart';
|
||||
import 'package:plezy/services/companion_remote/remote_auth_context.dart';
|
||||
|
||||
void main() {
|
||||
test('host and remote dispatch encrypted commands through the same contract', () async {
|
||||
final host = CompanionRemotePeerService();
|
||||
final remote = CompanionRemotePeerService();
|
||||
addTearDown(() async {
|
||||
await remote.dispose();
|
||||
await host.dispose();
|
||||
});
|
||||
|
||||
final context = RemoteAuthContext(
|
||||
id: 'context-1',
|
||||
backend: 'plex',
|
||||
connectionId: 'connection-1',
|
||||
homeSecret: List<int>.generate(32, (index) => index),
|
||||
discoveryKey: List<int>.generate(32, (index) => 255 - index),
|
||||
clientIdentifier: 'host-client',
|
||||
userUuid: 'user-1',
|
||||
allowedUserUuids: const ['user-1'],
|
||||
);
|
||||
|
||||
final session = await host.createSessionForContexts('Test Host', 'macos', [context]);
|
||||
await remote.joinSessionWithContexts(
|
||||
'Test Remote',
|
||||
'ios',
|
||||
'127.0.0.1:${session.port}',
|
||||
[context],
|
||||
authContextId: context.id,
|
||||
expectedHostClientId: context.clientIdentifier,
|
||||
);
|
||||
|
||||
final hostCommand = host.onCommandReceived.firstWhere((command) => command.type == RemoteCommandType.play);
|
||||
remote.sendCommand(const RemoteCommand(type: RemoteCommandType.play, data: {'source': 'remote'}));
|
||||
expect(
|
||||
await hostCommand.timeout(const Duration(seconds: 5)),
|
||||
const RemoteCommand(type: RemoteCommandType.play, data: {'source': 'remote'}),
|
||||
);
|
||||
|
||||
final remoteCommand = remote.onCommandReceived.firstWhere((command) => command.type == RemoteCommandType.pause);
|
||||
host.sendCommand(const RemoteCommand(type: RemoteCommandType.pause, data: {'source': 'host'}));
|
||||
expect(
|
||||
await remoteCommand.timeout(const Duration(seconds: 5)),
|
||||
const RemoteCommand(type: RemoteCommandType.pause, data: {'source': 'host'}),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import 'package:plezy/utils/media_server_http_client.dart';
|
||||
|
||||
import '../test_helpers/io_fakes.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
class _DelayedCountingHttpClient extends http.BaseClient {
|
||||
_DelayedCountingHttpClient(this.body);
|
||||
@@ -54,7 +55,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('buildArtworkSpecs includes all standard artwork with sanitized local keys', () {
|
||||
final item = MediaItem(
|
||||
final item = testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -29,6 +29,7 @@ import 'package:saf_util/saf_util_platform_interface.dart';
|
||||
|
||||
import '../test_helpers/io_fakes.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
group('downloadExtensionFromUrl', () {
|
||||
@@ -140,7 +141,7 @@ void main() {
|
||||
clientResolver: (serverId, {clientScopeId}) => null,
|
||||
);
|
||||
final year = await manager.debugResolveSafRecoveryShowYear(
|
||||
MediaItem(
|
||||
testMediaItem(
|
||||
id: 'ep-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -245,7 +246,7 @@ void main() {
|
||||
final client = _ArtworkRepairClient(
|
||||
serverId: ServerId('srv'),
|
||||
items: {
|
||||
'show-1': MediaItem(
|
||||
'show-1': testMediaItem(
|
||||
id: 'show-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.show,
|
||||
@@ -562,7 +563,7 @@ Future<_DeletionResult> _runEpisodeDeletion({required bool saf, bool failVideoDe
|
||||
JellyfinApiCache.initialize(db);
|
||||
final serverId = ServerId('srv');
|
||||
const globalKey = 'srv:episode-1';
|
||||
final episode = MediaItem(
|
||||
final episode = testMediaItem(
|
||||
id: 'episode-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -674,7 +675,7 @@ Future<_ContainerDeletionResult> _runContainerDeletion({required MediaKind kind,
|
||||
final serverId = ServerId('srv');
|
||||
final id = '${kind.id}-1';
|
||||
final globalKey = 'srv:$id';
|
||||
final metadata = MediaItem(
|
||||
final metadata = testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.plex,
|
||||
kind: kind,
|
||||
@@ -913,7 +914,7 @@ DownloadTask _downloadTask(String taskId, String globalKey) {
|
||||
}
|
||||
|
||||
MediaItem _movie({String? thumbPath}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:plezy/services/settings_service.dart';
|
||||
|
||||
import '../test_helpers/io_fakes.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tmpRoot;
|
||||
@@ -558,7 +559,7 @@ void main() {
|
||||
// ============================================================
|
||||
|
||||
MediaItem _movie({required String title, int? year}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 'm-${title.hashCode}',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -568,7 +569,7 @@ MediaItem _movie({required String title, int? year}) {
|
||||
}
|
||||
|
||||
MediaItem _show({required String title, int? year}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 's-${title.hashCode}',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.show,
|
||||
@@ -578,7 +579,7 @@ MediaItem _show({required String title, int? year}) {
|
||||
}
|
||||
|
||||
MediaItem _season({required String showTitle, int? showYear, required int seasonNumber}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 'season-$showTitle-$seasonNumber',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.season,
|
||||
@@ -596,7 +597,7 @@ MediaItem _episode({
|
||||
required int episodeNumber,
|
||||
required String episodeTitle,
|
||||
}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 'ep-$showTitle-$seasonNumber-$episodeNumber',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:plezy/services/data_aggregation_service.dart';
|
||||
import 'package:plezy/services/episode_navigation_service.dart';
|
||||
import 'package:plezy/services/multi_server_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
// NOTE on coverage scope:
|
||||
// `EpisodeNavigationService` has two methods:
|
||||
@@ -31,9 +32,9 @@ import 'package:provider/provider.dart';
|
||||
// the public surface callers depend on.
|
||||
|
||||
MediaItem _meta(String id, {String? title}) =>
|
||||
MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.episode, title: title ?? 'Episode $id');
|
||||
testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.episode, title: title ?? 'Episode $id');
|
||||
|
||||
MediaItem _jfEpisode(String id, {required String seriesId, ServerId? serverId}) => MediaItem(
|
||||
MediaItem _jfEpisode(String id, {required String seriesId, ServerId? serverId}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.episode,
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:plezy/services/external_player_service.dart';
|
||||
import 'package:plezy/services/jellyfin_api_cache.dart';
|
||||
import 'package:plezy/services/multi_server_manager.dart';
|
||||
import 'package:plezy/services/offline_watch_sync_service.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
class _RecordingClient implements MediaServerClient {
|
||||
_RecordingClient({this.backend = MediaBackend.plex});
|
||||
@@ -77,7 +78,7 @@ class _RecordingClient implements MediaServerClient {
|
||||
}
|
||||
|
||||
MediaItem _item({int? durationMs}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:plezy/utils/device_identity.dart';
|
||||
|
||||
import '../test_helpers/backend_client_fixtures.dart';
|
||||
import '../test_helpers/paged_fakes.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
JellyfinConnection _conn({String accessToken = 'tok-abc', String baseUrl = 'https://jf.example.com'}) =>
|
||||
testJellyfinConnection(
|
||||
@@ -423,7 +424,7 @@ void main() {
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final resolution = await scoped.resolveDownload(
|
||||
MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
testMediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
mediaIndex: 1,
|
||||
);
|
||||
|
||||
@@ -470,7 +471,7 @@ void main() {
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final url = await scoped.resolveExternalPlaybackUrl(
|
||||
MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
testMediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
mediaIndex: 0,
|
||||
mediaSourceId: 'item-1',
|
||||
);
|
||||
@@ -535,7 +536,7 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
@@ -611,7 +612,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
qualityPreset: TranscodeQualityPreset.p720_2mbps,
|
||||
),
|
||||
@@ -694,7 +700,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
),
|
||||
);
|
||||
@@ -795,7 +806,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
),
|
||||
);
|
||||
@@ -857,7 +873,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
),
|
||||
);
|
||||
@@ -918,7 +939,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
selectedAudioStreamId: 4,
|
||||
),
|
||||
@@ -986,7 +1012,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 1,
|
||||
selectedAudioStreamId: 4,
|
||||
),
|
||||
@@ -1048,7 +1079,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
selectedMediaSourceId: 'src-1080',
|
||||
),
|
||||
@@ -1108,7 +1144,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
selectedMediaSourceId: 'item-1',
|
||||
),
|
||||
@@ -1176,7 +1217,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
selectedMediaSourceId: 'src-1080',
|
||||
),
|
||||
@@ -1265,7 +1311,7 @@ void main() {
|
||||
);
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final item = MediaItem(
|
||||
final item = testMediaItem(
|
||||
id: 'folder/item #1?x',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
@@ -1303,7 +1349,12 @@ void main() {
|
||||
);
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final item = MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1');
|
||||
final item = testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
|
||||
await expectLater(scoped.removeFromContinueWatching(item), throwsA(isA<UnsupportedError>()));
|
||||
expect(requested, isFalse);
|
||||
@@ -1345,7 +1396,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
qualityPreset: TranscodeQualityPreset.p720_2mbps,
|
||||
),
|
||||
@@ -1386,7 +1442,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
),
|
||||
);
|
||||
@@ -1578,7 +1639,12 @@ void main() {
|
||||
|
||||
final result = await scoped.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-1',
|
||||
),
|
||||
selectedMediaIndex: 0,
|
||||
qualityPreset: TranscodeQualityPreset.p720_2mbps,
|
||||
),
|
||||
@@ -1883,7 +1949,7 @@ void main() {
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final items = await scoped.fetchFolderChildren(
|
||||
MediaItem(id: 'folder-1', backend: MediaBackend.jellyfin, kind: MediaKind.folder),
|
||||
testMediaItem(id: 'folder-1', backend: MediaBackend.jellyfin, kind: MediaKind.folder),
|
||||
onPage: pages.add,
|
||||
);
|
||||
|
||||
@@ -1925,7 +1991,7 @@ void main() {
|
||||
addTearDown(scoped.close);
|
||||
|
||||
final items = await scoped.fetchFolderChildren(
|
||||
MediaItem(id: 'season-1', backend: MediaBackend.jellyfin, kind: MediaKind.season),
|
||||
testMediaItem(id: 'season-1', backend: MediaBackend.jellyfin, kind: MediaKind.season),
|
||||
onPage: pages.add,
|
||||
);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:plezy/services/playlist_items_loader.dart';
|
||||
import 'package:plezy/utils/media_server_http_client.dart';
|
||||
|
||||
import '../test_helpers/paged_fakes.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
/// Recording fake that satisfies [JellyfinClient] via `implements` +
|
||||
/// `noSuchMethod`. The launcher only needs the
|
||||
@@ -76,7 +77,7 @@ class _RecordingJellyfinClient implements JellyfinClient {
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
MediaItem _ep(String id, {ServerId? serverId}) => MediaItem(
|
||||
MediaItem _ep(String id, {ServerId? serverId}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.episode,
|
||||
@@ -84,7 +85,7 @@ MediaItem _ep(String id, {ServerId? serverId}) => MediaItem(
|
||||
serverId: serverId ?? ServerId('srv-jf'),
|
||||
);
|
||||
|
||||
MediaItem _movie(String id, {ServerId? serverId}) => MediaItem(
|
||||
MediaItem _movie(String id, {ServerId? serverId}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
@@ -92,7 +93,7 @@ MediaItem _movie(String id, {ServerId? serverId}) => MediaItem(
|
||||
serverId: serverId ?? ServerId('srv-jf'),
|
||||
);
|
||||
|
||||
MediaItem _clip(String id, {ServerId? serverId}) => MediaItem(
|
||||
MediaItem _clip(String id, {ServerId? serverId}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.clip,
|
||||
@@ -100,7 +101,7 @@ MediaItem _clip(String id, {ServerId? serverId}) => MediaItem(
|
||||
serverId: serverId ?? ServerId('srv-jf'),
|
||||
);
|
||||
|
||||
MediaItem _track(String id, {ServerId? serverId}) => MediaItem(
|
||||
MediaItem _track(String id, {ServerId? serverId}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.track,
|
||||
@@ -146,7 +147,7 @@ void main() {
|
||||
final ctx = await pumpContext(tester);
|
||||
final launcher = JellyfinSequentialLauncher(context: ctx);
|
||||
|
||||
final orphan = MediaItem(
|
||||
final orphan = testMediaItem(
|
||||
id: 'col-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
@@ -173,7 +174,7 @@ void main() {
|
||||
navigateForTesting: (m) async => navigated.add(m),
|
||||
);
|
||||
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'col-99',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
@@ -273,7 +274,12 @@ void main() {
|
||||
// container. If a future change reverts to fetchChildren the test
|
||||
// fails because a Series row would leak into the queue.
|
||||
final ctx = await pumpContext(tester);
|
||||
final movie = MediaItem(id: 'movie-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-jf');
|
||||
final movie = testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
serverId: 'srv-jf',
|
||||
);
|
||||
final ep1 = _ep('series-A-ep1');
|
||||
final ep2 = _ep('series-A-ep2');
|
||||
final fakeClient = _RecordingJellyfinClient(playableDescendantsResponse: [movie, ep1, ep2]);
|
||||
@@ -287,7 +293,7 @@ void main() {
|
||||
navigateForTesting: (m) async => navigated.add(m),
|
||||
);
|
||||
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'col-mixed',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
@@ -323,7 +329,7 @@ void main() {
|
||||
navigateForTesting: (_) async {},
|
||||
);
|
||||
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'col-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
@@ -360,7 +366,7 @@ void main() {
|
||||
navigateForTesting: (m) async => navigated.add(m),
|
||||
);
|
||||
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'col-start',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
@@ -394,7 +400,7 @@ void main() {
|
||||
navigateForTesting: (m) async => navigated.add(m),
|
||||
);
|
||||
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'col',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
@@ -427,7 +433,7 @@ void main() {
|
||||
navigateForTesting: (m) async => navigated.add(m),
|
||||
);
|
||||
|
||||
final folder = MediaItem(
|
||||
final folder = testMediaItem(
|
||||
id: 'folder-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.unknown,
|
||||
@@ -465,7 +471,7 @@ void main() {
|
||||
navigateForTesting: (_) async {},
|
||||
);
|
||||
|
||||
final folder = MediaItem(
|
||||
final folder = testMediaItem(
|
||||
id: 'folder-shuffle',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.unknown,
|
||||
@@ -497,7 +503,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final folder = MediaItem(
|
||||
final folder = testMediaItem(
|
||||
id: 'music-folder',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.unknown,
|
||||
@@ -515,7 +521,7 @@ void main() {
|
||||
final ctx = await pumpContext(tester);
|
||||
final launcher = JellyfinSequentialLauncher(context: ctx);
|
||||
|
||||
final movie = MediaItem(id: 'm1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-jf');
|
||||
final movie = testMediaItem(id: 'm1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-jf');
|
||||
|
||||
final result = await launcher.launchShuffledShow(metadata: movie, showLoadingIndicator: false);
|
||||
|
||||
@@ -527,7 +533,12 @@ void main() {
|
||||
final ctx = await pumpContext(tester);
|
||||
final launcher = JellyfinSequentialLauncher(context: ctx);
|
||||
|
||||
final season = MediaItem(id: 's1', backend: MediaBackend.jellyfin, kind: MediaKind.season, serverId: 'srv-jf');
|
||||
final season = testMediaItem(
|
||||
id: 's1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.season,
|
||||
serverId: 'srv-jf',
|
||||
);
|
||||
|
||||
final result = await launcher.launchShuffledShow(metadata: season, showLoadingIndicator: false);
|
||||
|
||||
@@ -539,7 +550,7 @@ void main() {
|
||||
final ctx = await pumpContext(tester);
|
||||
final launcher = JellyfinSequentialLauncher(context: ctx);
|
||||
|
||||
final orphan = MediaItem(id: 'show-orphan', backend: MediaBackend.jellyfin, kind: MediaKind.show);
|
||||
final orphan = testMediaItem(id: 'show-orphan', backend: MediaBackend.jellyfin, kind: MediaKind.show);
|
||||
|
||||
final result = await launcher.launchShuffledShow(metadata: orphan, showLoadingIndicator: false);
|
||||
|
||||
@@ -563,7 +574,7 @@ void main() {
|
||||
navigateForTesting: (m) async => navigated.add(m),
|
||||
);
|
||||
|
||||
final show = MediaItem(
|
||||
final show = testMediaItem(
|
||||
id: 'show-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.show,
|
||||
@@ -599,7 +610,7 @@ void main() {
|
||||
navigateForTesting: (_) async {},
|
||||
);
|
||||
|
||||
final season = MediaItem(
|
||||
final season = testMediaItem(
|
||||
id: 'season-2',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.season,
|
||||
@@ -628,7 +639,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final show = MediaItem(
|
||||
final show = testMediaItem(
|
||||
id: 'show-empty',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.show,
|
||||
@@ -657,7 +668,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'col-empty',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.collection,
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:plezy/services/local_playback_history.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
@@ -15,7 +16,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('recordPlayback writes item and series keys for an episode', () async {
|
||||
final episode = MediaItem(
|
||||
final episode = testMediaItem(
|
||||
id: 'ep-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -32,7 +33,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('recordPlayback writes only the item key for a movie', () async {
|
||||
final movie = MediaItem(
|
||||
final movie = testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -47,7 +48,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('repeat writes for the same item within the rewrite window are skipped', () async {
|
||||
final movie = MediaItem(
|
||||
final movie = testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -67,7 +68,7 @@ void main() {
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.write(SettingsService.localLastPlayedAt, {for (var i = 0; i < 400; i++) 'srv-1:old-$i': i + 1});
|
||||
|
||||
final movie = MediaItem(
|
||||
final movie = testMediaItem(
|
||||
id: 'fresh',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -19,10 +19,11 @@ import 'package:plezy/services/music/music_playback_service.dart';
|
||||
import 'package:plezy/services/music/music_playback_service_impl.dart';
|
||||
import 'package:plezy/services/music/music_source_resolver.dart';
|
||||
import 'package:plezy/services/playback_coordinator.dart';
|
||||
import '../../test_helpers/media_items.dart';
|
||||
|
||||
const _trackDuration = Duration(minutes: 3);
|
||||
|
||||
MediaItem _track(String id) => MediaItem(
|
||||
MediaItem _track(String id) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.track,
|
||||
|
||||
@@ -6,9 +6,10 @@ import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/services/music/music_playback_service.dart';
|
||||
import 'package:plezy/services/music/music_queue_controller.dart';
|
||||
import '../../test_helpers/media_items.dart';
|
||||
|
||||
MediaItem _track(String id) =>
|
||||
MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: 'Track $id', serverId: 'srv');
|
||||
testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: 'Track $id', serverId: 'srv');
|
||||
|
||||
List<String> _ids(List<MediaItem> items) => [for (final i in items) i.id];
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'package:plezy/utils/watch_state_notifier.dart';
|
||||
|
||||
import '../test_helpers/backend_client_fixtures.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
// NOTE on coverage scope:
|
||||
// The actual sync-to-server path (`syncPendingItems`, `syncWatchStatesFromServer`,
|
||||
@@ -93,7 +94,7 @@ class _RecordingMediaClient implements MediaServerClient {
|
||||
|
||||
@override
|
||||
Future<MediaItem?> fetchItem(String id) async =>
|
||||
MediaItem(id: id, backend: backend, kind: MediaKind.movie, serverId: serverId);
|
||||
testMediaItem(id: id, backend: backend, kind: MediaKind.movie, serverId: serverId);
|
||||
|
||||
@override
|
||||
Future<void> reportPlaybackStarted({
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/services/play_queue_launcher.dart';
|
||||
import 'package:plezy/services/plex_client.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
// NOTE on coverage scope:
|
||||
// `PlayQueueLauncher` is almost entirely network/UI glue:
|
||||
@@ -82,7 +83,7 @@ void main() {
|
||||
final launcher = PlexPlayQueueLauncher(context: capturedContext, client: _StubPlexClient());
|
||||
final result = await launcher.launchShuffledShow(
|
||||
// movie is not show / season.
|
||||
metadata: MediaItem(id: 'rk1', backend: MediaBackend.plex, kind: MediaKind.movie),
|
||||
metadata: testMediaItem(id: 'rk1', backend: MediaBackend.plex, kind: MediaKind.movie),
|
||||
showLoadingIndicator: false,
|
||||
);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import 'package:plezy/services/settings_service.dart';
|
||||
|
||||
import '../test_helpers/io_fakes.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -60,7 +61,7 @@ void main() {
|
||||
await PlexApiCache.instance.put(ServerId('srv-1'), '/library/metadata/movie-1', _plexMetadataEnvelope());
|
||||
|
||||
final result = await PlaybackInitializationService(database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -89,7 +90,7 @@ void main() {
|
||||
final client = _FailingPlaybackClient(serverId: ServerId('srv-1'));
|
||||
|
||||
final result = await PlaybackInitializationService(client: client, database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'track-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.track,
|
||||
@@ -116,7 +117,7 @@ void main() {
|
||||
final client = _FailingPlaybackClient(serverId: ServerId('srv-1'));
|
||||
|
||||
final result = await PlaybackInitializationService(client: client, database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -148,7 +149,7 @@ void main() {
|
||||
);
|
||||
|
||||
final result = await PlaybackInitializationService(database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -181,7 +182,7 @@ void main() {
|
||||
);
|
||||
|
||||
final result = await PlaybackInitializationService(database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -208,7 +209,7 @@ void main() {
|
||||
);
|
||||
|
||||
final result = await PlaybackInitializationService(database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -238,7 +239,7 @@ void main() {
|
||||
final client = _StreamingPlaybackClient(serverId: ServerId('srv-1'));
|
||||
|
||||
final result = await PlaybackInitializationService(client: client, database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
@@ -292,7 +293,7 @@ void main() {
|
||||
);
|
||||
|
||||
final result = await PlaybackInitializationService(database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'item-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.movie,
|
||||
@@ -320,7 +321,7 @@ void main() {
|
||||
await subtitleFile.writeAsString('1\n00:00:00,000 --> 00:00:01,000\nHello');
|
||||
|
||||
final result = await PlaybackInitializationService(database: db).getPlaybackData(
|
||||
metadata: MediaItem(
|
||||
metadata: testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:plezy/services/plex_client.dart';
|
||||
import 'package:plezy/utils/watch_state_notifier.dart';
|
||||
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
// NOTE on coverage scope:
|
||||
// `PlaybackProgressTracker` periodically samples the player's position and
|
||||
@@ -291,13 +292,14 @@ class _StopMarksWatchedClient extends _FakePlexClient {
|
||||
|
||||
const Object _defaultServerId = Object();
|
||||
|
||||
MediaItem _meta({String ratingKey = '42', Object? serverId = _defaultServerId, String? type = 'movie'}) => MediaItem(
|
||||
id: ratingKey,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.fromString(type),
|
||||
title: 'Test Item',
|
||||
serverId: identical(serverId, _defaultServerId) ? ServerId('srv') : serverId as ServerId?,
|
||||
);
|
||||
MediaItem _meta({String ratingKey = '42', Object? serverId = _defaultServerId, String? type = 'movie'}) =>
|
||||
testMediaItem(
|
||||
id: ratingKey,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.fromString(type),
|
||||
title: 'Test Item',
|
||||
serverId: identical(serverId, _defaultServerId) ? ServerId('srv') : serverId as ServerId?,
|
||||
);
|
||||
|
||||
void main() {
|
||||
setUp(resetSharedPreferencesForTest);
|
||||
@@ -593,7 +595,7 @@ void main() {
|
||||
);
|
||||
final tracker = PlaybackProgressTracker(
|
||||
client: client,
|
||||
metadata: MediaItem(id: '42', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
|
||||
metadata: testMediaItem(id: '42', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
|
||||
player: player,
|
||||
isOffline: false,
|
||||
mediaInfo: mediaInfo,
|
||||
|
||||
@@ -7,10 +7,11 @@ import 'package:plezy/models/transcode_quality_preset.dart';
|
||||
import 'package:plezy/services/playback_context.dart';
|
||||
import 'package:plezy/services/playback_initialization_types.dart';
|
||||
import 'package:plezy/services/playback_session.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
PlaybackContext _context(PlaybackInitializationResult result) {
|
||||
return PlaybackContext(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
||||
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
||||
result: result,
|
||||
sourceKind: result.usesLocalMedia ? PlaybackSourceKind.localFile : PlaybackSourceKind.remoteDirect,
|
||||
reportingMode: PlaybackReportingMode.online,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:plezy/services/multi_server_manager.dart';
|
||||
import 'package:plezy/services/playback_context.dart';
|
||||
import 'package:plezy/services/playback_initialization_types.dart';
|
||||
import 'package:plezy/services/playback_source_resolver.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
class _PlaybackClient implements MediaServerClient {
|
||||
_PlaybackClient({this.clientBackend = MediaBackend.plex, PlaybackInitializationResult? result})
|
||||
@@ -56,7 +57,7 @@ void main() {
|
||||
manager.debugRegisterClientForTesting(client, online: false);
|
||||
|
||||
final context = await PlaybackSourceResolver(serverManager: manager, database: db).resolve(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
||||
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
||||
selectedMediaIndex: 0,
|
||||
offlineLibraryMode: false,
|
||||
qualityPreset: TranscodeQualityPreset.original,
|
||||
@@ -79,7 +80,7 @@ void main() {
|
||||
manager.debugRegisterClientForTesting(client, online: true);
|
||||
|
||||
final context = await PlaybackSourceResolver(serverManager: manager, database: db).resolve(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
||||
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
|
||||
selectedMediaIndex: 0,
|
||||
offlineLibraryMode: false,
|
||||
qualityPreset: TranscodeQualityPreset.original,
|
||||
@@ -103,7 +104,7 @@ void main() {
|
||||
manager.debugRegisterClientForTesting(client, online: true);
|
||||
|
||||
final context = await PlaybackSourceResolver(serverManager: manager, database: db).resolve(
|
||||
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
|
||||
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
|
||||
selectedMediaIndex: 0,
|
||||
offlineLibraryMode: false,
|
||||
qualityPreset: TranscodeQualityPreset.original,
|
||||
|
||||
@@ -5,7 +5,11 @@ 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/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 '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase db;
|
||||
@@ -54,6 +58,47 @@ void main() {
|
||||
test('database getter exposes the underlying AppDatabase', () {
|
||||
expect(identical(cache.database, db), isTrue);
|
||||
});
|
||||
|
||||
test('registered cleanup ignores backend initialization order and preserves pinned rows', () async {
|
||||
await cache.put(ServerId('srv'), '/volatile', {'value': 1});
|
||||
await cache.put(ServerId('srv'), '/pinned', {'value': 2});
|
||||
await cache.pin(ServerId('srv'), '/pinned');
|
||||
|
||||
// Register the other backend last; cleanup must not depend on whichever
|
||||
// concrete singleton happened to initialize most recently.
|
||||
JellyfinApiCache.initialize(db);
|
||||
await ApiCache.clearRegisteredVolatile();
|
||||
|
||||
expect(await cache.get(ServerId('srv'), '/volatile'), isNull);
|
||||
expect(await cache.get(ServerId('srv'), '/pinned'), {'value': 2});
|
||||
});
|
||||
|
||||
test('registering a new database drops stale backend dispatch entries', () async {
|
||||
final newDb = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
JellyfinApiCache.initialize(newDb);
|
||||
|
||||
expect(() => ApiCache.forBackend(MediaBackend.plex), throwsStateError);
|
||||
expect(identical(ApiCache.forBackend(MediaBackend.jellyfin).database, newDb), isTrue);
|
||||
|
||||
await newDb.close();
|
||||
});
|
||||
});
|
||||
|
||||
group('shared row decoding', () {
|
||||
test('drops malformed rows without discarding valid siblings', () {
|
||||
final decoded = decodeCachedMediaRows(
|
||||
['{"id":"first"}', 'not json', '[]', '{"id":"last"}'],
|
||||
serializedData: (row) => row,
|
||||
decode: (_, json) {
|
||||
final id = json['id'] as String;
|
||||
return MapEntry(id, testMediaItem(id: id));
|
||||
},
|
||||
);
|
||||
|
||||
expect(decoded.keys, ['first', 'last']);
|
||||
expect(decoded['first']?.id, 'first');
|
||||
expect(decoded['last']?.id, 'last');
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/services/plex_client.dart';
|
||||
|
||||
import '../test_helpers/backend_client_fixtures.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase db;
|
||||
@@ -315,7 +316,7 @@ void main() {
|
||||
|
||||
final result = await client.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
|
||||
metadata: testMediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
|
||||
selectedMediaIndex: 0,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/services/plex_client.dart';
|
||||
|
||||
import '../test_helpers/backend_client_fixtures.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
/// Regression coverage for the Plex transcode reporting bug: while
|
||||
/// transcoding, the `/:/timeline` reports must carry the playback's
|
||||
@@ -165,7 +166,7 @@ void main() {
|
||||
|
||||
final result = await client.getPlaybackInitialization(
|
||||
PlaybackInitializationOptions(
|
||||
metadata: MediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
|
||||
metadata: testMediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
|
||||
selectedMediaIndex: 0,
|
||||
// Original preset stays on the direct-play branch (no transcode
|
||||
// decision round-trip needed for this assertion).
|
||||
|
||||
@@ -19,6 +19,7 @@ import 'package:plezy/services/sync_rule_executor.dart';
|
||||
|
||||
import '../test_helpers/backend_client_fixtures.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
JellyfinConnection _jellyfinConnection(String userId) => testJellyfinConnection(
|
||||
machineId: 'jf-machine',
|
||||
@@ -314,7 +315,7 @@ void main() {
|
||||
manager.debugRegisterClientForTesting(client);
|
||||
|
||||
const ruleKey = 'profile-a|plex-machine:show-1';
|
||||
final show = MediaItem(id: 'show-1', backend: MediaBackend.plex, kind: MediaKind.show, title: 'Show');
|
||||
final show = testMediaItem(id: 'show-1', backend: MediaBackend.plex, kind: MediaKind.show, title: 'Show');
|
||||
await db.insertSyncRule(
|
||||
profileId: 'profile-a',
|
||||
serverId: ServerId('plex-machine'),
|
||||
@@ -357,7 +358,7 @@ void main() {
|
||||
manager.debugRegisterClientForTesting(client);
|
||||
|
||||
const ruleKey = 'profile-a|plex-machine:collection-1';
|
||||
final collection = MediaItem(
|
||||
final collection = testMediaItem(
|
||||
id: 'collection-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.collection,
|
||||
@@ -406,10 +407,10 @@ void main() {
|
||||
|
||||
final items = [
|
||||
_track('loose-track'),
|
||||
MediaItem(id: 'album-1', backend: MediaBackend.plex, kind: MediaKind.album, title: 'Album'),
|
||||
MediaItem(id: 'artist-1', backend: MediaBackend.plex, kind: MediaKind.artist, title: 'Artist'),
|
||||
testMediaItem(id: 'album-1', backend: MediaBackend.plex, kind: MediaKind.album, title: 'Album'),
|
||||
testMediaItem(id: 'artist-1', backend: MediaBackend.plex, kind: MediaKind.artist, title: 'Artist'),
|
||||
// Still skipped: nested lists / unplayable kinds.
|
||||
MediaItem(id: 'photo-1', backend: MediaBackend.plex, kind: MediaKind.photo, title: 'Photo'),
|
||||
testMediaItem(id: 'photo-1', backend: MediaBackend.plex, kind: MediaKind.photo, title: 'Photo'),
|
||||
];
|
||||
|
||||
final out = <MediaItem>[];
|
||||
@@ -431,11 +432,11 @@ void main() {
|
||||
}
|
||||
|
||||
MediaItem _track(String id, {bool played = false}) {
|
||||
return MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: id, viewCount: played ? 1 : 0);
|
||||
return testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: id, viewCount: played ? 1 : 0);
|
||||
}
|
||||
|
||||
MediaItem _episode(String id, {required int parentIndex, required int index, String? originallyAvailableAt}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -526,7 +527,7 @@ class _CollectionPagingClient implements MediaServerClient {
|
||||
collectionPageCalls.add((start: start, size: size));
|
||||
expect(collectionId, 'collection-1');
|
||||
return LibraryPage(
|
||||
items: [MediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie')],
|
||||
items: [testMediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie')],
|
||||
totalCount: 1,
|
||||
offset: start ?? 0,
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/services/track_manager.dart';
|
||||
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
// NOTE on coverage scope:
|
||||
// `TrackManager` orchestrates the player + Plex client + SettingsService
|
||||
@@ -42,7 +43,7 @@ import '../test_helpers/prefs.dart';
|
||||
// therefore gated on the same SettingsService dependency.
|
||||
// - `resumeAfterSubtitleLoad` — schedules a real wall-clock fallback Timer.
|
||||
|
||||
MediaItem _meta({String id = 'rk1'}) => MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.movie);
|
||||
MediaItem _meta({String id = 'rk1'}) => testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.movie);
|
||||
|
||||
/// Player that records calls and can be configured per-test.
|
||||
class _FakePlayer with PlayerStreamControllersMixin implements Player {
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:plezy/models/jellyfin/jellyfin_user_profile.dart';
|
||||
import 'package:plezy/models/plex/plex_user_profile.dart';
|
||||
import 'package:plezy/mpv/mpv.dart';
|
||||
import 'package:plezy/services/track_selection_service.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
// NOTE on coverage scope:
|
||||
// `TrackSelectionService` is a large pure logic surface with one async
|
||||
@@ -44,7 +45,7 @@ import 'package:plezy/services/track_selection_service.dart';
|
||||
// ============================================================
|
||||
|
||||
MediaItem _meta({MediaBackend backend = MediaBackend.plex, String? audioLanguage, String? subtitleLanguage}) =>
|
||||
MediaItem(
|
||||
testMediaItem(
|
||||
id: 'rk1',
|
||||
backend: backend,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/media/media_server_client.dart';
|
||||
import 'package:plezy/models/trackers/anime_lists_mapping.dart';
|
||||
import 'package:plezy/services/trackers/anime_episode_progress_resolver.dart';
|
||||
import '../../test_helpers/media_items.dart';
|
||||
|
||||
class _FakeMediaServerClient implements MediaServerClient {
|
||||
final Map<String, List<MediaItem>> childrenByParent;
|
||||
@@ -33,7 +34,7 @@ class _FakeMediaServerClient implements MediaServerClient {
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
MediaItem _season(int number, {int? watched, int? total}) => MediaItem(
|
||||
MediaItem _season(int number, {int? watched, int? total}) => testMediaItem(
|
||||
id: 'season-$number',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.season,
|
||||
@@ -43,7 +44,7 @@ MediaItem _season(int number, {int? watched, int? total}) => MediaItem(
|
||||
viewedLeafCount: watched,
|
||||
);
|
||||
|
||||
MediaItem _episode({int season = 2, int number = 6, String showId = 'show-1', int? viewCount}) => MediaItem(
|
||||
MediaItem _episode({int season = 2, int number = 6, String showId = 'show-1', int? viewCount}) => testMediaItem(
|
||||
id: 'episode-$season-$number',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:plezy/services/trackers/simkl/simkl_tracker.dart';
|
||||
import 'package:plezy/services/trackers/tracker_coordinator.dart';
|
||||
import 'package:plezy/services/trackers/tracker_session.dart';
|
||||
import 'package:plezy/utils/external_ids.dart';
|
||||
import '../../test_helpers/media_items.dart';
|
||||
|
||||
class _FakeMediaServerClient implements MediaServerClient {
|
||||
@override
|
||||
@@ -88,7 +89,7 @@ class _FakeAnimeListsLookup implements AnimeListsMappingLookup {
|
||||
Future<Set<int>> lookupAnimeIdsForShow({int? tvdbId, int? tmdbId}) async => const <int>{};
|
||||
}
|
||||
|
||||
MediaItem _season() => MediaItem(
|
||||
MediaItem _season() => testMediaItem(
|
||||
id: 'season-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.season,
|
||||
@@ -99,7 +100,7 @@ MediaItem _season() => MediaItem(
|
||||
parentId: 'show-1',
|
||||
);
|
||||
|
||||
MediaItem _episode(int number, {int season = 1}) => MediaItem(
|
||||
MediaItem _episode(int number, {int season = 1}) => testMediaItem(
|
||||
id: 'episode-$season-$number',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -110,7 +111,7 @@ MediaItem _episode(int number, {int season = 1}) => MediaItem(
|
||||
index: number,
|
||||
);
|
||||
|
||||
MediaItem _show() => MediaItem(
|
||||
MediaItem _show() => testMediaItem(
|
||||
id: 'show-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.show,
|
||||
@@ -119,7 +120,7 @@ MediaItem _show() => MediaItem(
|
||||
libraryId: 'lib-1',
|
||||
);
|
||||
|
||||
MediaItem _movie() => MediaItem(
|
||||
MediaItem _movie() => testMediaItem(
|
||||
id: 'movie-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:plezy/services/trackers/anime_lists_mapping_store.dart';
|
||||
import 'package:plezy/services/trackers/fribb_mapping_store.dart';
|
||||
import 'package:plezy/services/trackers/tracker_id_resolver.dart';
|
||||
import 'package:plezy/utils/external_ids.dart';
|
||||
import '../../test_helpers/media_items.dart';
|
||||
|
||||
class _FakeMediaServerClient implements MediaServerClient {
|
||||
final Map<String, ExternalIds> externalIdsByItem;
|
||||
@@ -94,7 +95,7 @@ class _FakeAnimeListsLookup implements AnimeListsMappingLookup {
|
||||
Future<Set<int>> lookupAnimeIdsForShow({int? tvdbId, int? tmdbId}) async => const <int>{};
|
||||
}
|
||||
|
||||
MediaItem _episode({int season = 23, int number = 6}) => MediaItem(
|
||||
MediaItem _episode({int season = 23, int number = 6}) => testMediaItem(
|
||||
id: 'episode-$season-$number',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/services/watch_state_resolver.dart';
|
||||
import 'package:plezy/utils/watch_state_notifier.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
OfflineWatchProgressItem _action({
|
||||
required String actionType,
|
||||
@@ -86,7 +87,7 @@ void main() {
|
||||
|
||||
test('applying a watched snapshot patches container leaf counts so isWatched flips', () {
|
||||
const snapshot = WatchStateSnapshot(isWatched: true, hasViewOffsetMs: true, viewOffsetMs: 0);
|
||||
final season = MediaItem(
|
||||
final season = testMediaItem(
|
||||
id: 'season-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.season,
|
||||
|
||||
Reference in New Issue
Block a user