refactor(core): consolidate shared app foundations

This commit is contained in:
edde746
2026-07-12 17:31:12 +02:00
parent a481fc1000
commit 97f7508067
59 changed files with 1244 additions and 919 deletions
@@ -20,14 +20,10 @@ import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import 'package:plezy/services/settings_service.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
JellyfinConnection _conn() => JellyfinConnection(
id: 'srv-1/user-1',
baseUrl: 'https://jf.example.com',
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
JellyfinConnection _conn() => testJellyfinConnection(
userName: 'edde',
accessToken: 'tok-abc',
deviceId: 'dev-xyz',
@@ -17,46 +17,10 @@ import 'package:plezy/services/download_artwork_service.dart';
import 'package:plezy/services/download_storage_service.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/utils/media_server_http_client.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
class _FakePathProvider extends PathProviderPlatform with MockPlatformInterfaceMixin {
_FakePathProvider(this.root);
final Directory root;
@override
Future<String?> getApplicationDocumentsPath() async => _ensure('documents');
@override
Future<String?> getApplicationSupportPath() async => _ensure('support');
@override
Future<String?> getApplicationCachePath() async => _ensure('cache');
@override
Future<String?> getTemporaryPath() async => _ensure('temp');
String _ensure(String name) {
final path = p.join(root.path, name);
Directory(path).createSync(recursive: true);
return path;
}
}
class _FakeHttpClient extends http.BaseClient {
_FakeHttpClient(this.statusCode, this.body);
final int statusCode;
final List<int> body;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
return http.StreamedResponse(Stream<List<int>>.value(body), statusCode, request: request);
}
}
class _DelayedCountingHttpClient extends http.BaseClient {
_DelayedCountingHttpClient(this.body);
@@ -80,7 +44,7 @@ void main() {
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
tmpRoot = await Directory.systemTemp.createTemp('download_artwork_service_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
});
tearDown(() async {
@@ -114,7 +78,7 @@ void main() {
await storage.initialize(settings);
final service = DownloadArtworkService(
storageService: storage,
http: MediaServerHttpClient(client: _FakeHttpClient(200, utf8.encode('image'))),
http: MediaServerHttpClient(client: FakeHttpClient(200, utf8.encode('image'))),
);
const tokenized = 'https://jf/Items/1/Images/Logo?tag=abc&api_key=secret';
@@ -125,7 +89,7 @@ void main() {
test('downloadFile rejects non-success responses without leaving final files', () async {
final file = File(p.join(tmpRoot.path, 'art.jpg'));
final httpClient = MediaServerHttpClient(client: _FakeHttpClient(404, utf8.encode('not found')));
final httpClient = MediaServerHttpClient(client: FakeHttpClient(404, utf8.encode('not found')));
await expectLater(
httpClient.downloadFile('https://example.test/art.jpg', file.path),
@@ -143,7 +107,7 @@ void main() {
final body = utf8.encode('valid image bytes');
final service = DownloadArtworkService(
storageService: storage,
http: MediaServerHttpClient(client: _FakeHttpClient(200, body)),
http: MediaServerHttpClient(client: FakeHttpClient(200, body)),
);
const rawPath = 'https://jf/Items/1/Images/Logo?tag=abc&api_key=secret';
@@ -6,7 +6,6 @@ import 'package:background_downloader/background_downloader.dart';
import 'package:drift/drift.dart' show Value;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plezy/database/app_database.dart';
@@ -26,9 +25,9 @@ import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/saf_storage_service.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/utils/media_server_http_client.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:saf_util/saf_util_platform_interface.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
void main() {
@@ -190,7 +189,7 @@ void main() {
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
final tmpRoot = await Directory.systemTemp.createTemp('download_manager_artwork_repair_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
addTearDown(() async {
DownloadStorageService.resetForTesting();
SettingsService.resetForTesting();
@@ -263,7 +262,7 @@ void main() {
database: db,
storageService: storage,
clientResolver: (serverId, {clientScopeId}) => client,
http: MediaServerHttpClient(client: _FakeHttpClient(200, utf8.encode('image bytes'))),
http: MediaServerHttpClient(client: FakeHttpClient(200, utf8.encode('image bytes'))),
);
await manager.repairMissingArtworkForDownloads();
@@ -284,7 +283,7 @@ void main() {
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
final tmpRoot = await Directory.systemTemp.createTemp('download_manager_delete_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
addTearDown(() async {
DownloadStorageService.resetForTesting();
SettingsService.resetForTesting();
@@ -551,7 +550,7 @@ Future<_DeletionResult> _runEpisodeDeletion({required bool saf, bool failVideoDe
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
final tmpRoot = await Directory.systemTemp.createTemp('download_manager_backend_delete_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
final storage = saf ? DownloadStorageService.forTestingSaf('content://downloads') : DownloadStorageService.instance;
if (!saf) {
@@ -664,7 +663,7 @@ Future<_ContainerDeletionResult> _runContainerDeletion({required MediaKind kind,
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
final tmpRoot = await Directory.systemTemp.createTemp('download_manager_container_delete_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
final storage = saf ? DownloadStorageService.forTestingSaf('content://downloads') : DownloadStorageService.instance;
if (!saf) await storage.initialize(await SettingsService.getInstance());
@@ -939,42 +938,6 @@ class _ScopedJellyfinClient implements MediaServerClient, ScopedMediaServerClien
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class _FakePathProvider extends PathProviderPlatform with MockPlatformInterfaceMixin {
_FakePathProvider(this.root);
final Directory root;
@override
Future<String?> getApplicationDocumentsPath() async => _ensure('documents');
@override
Future<String?> getApplicationSupportPath() async => _ensure('support');
@override
Future<String?> getApplicationCachePath() async => _ensure('cache');
@override
Future<String?> getTemporaryPath() async => _ensure('temp');
String _ensure(String name) {
final path = p.join(root.path, name);
Directory(path).createSync(recursive: true);
return path;
}
}
class _FakeHttpClient extends http.BaseClient {
_FakeHttpClient(this.statusCode, this.body);
final int statusCode;
final List<int> body;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
return http.StreamedResponse(Stream<List<int>>.value(body), statusCode, request: request);
}
}
class _ArtworkRepairClient implements MediaServerClient {
_ArtworkRepairClient({required this.serverId, required this.items});
@@ -9,42 +9,10 @@ import 'package:plezy/media/media_item.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/services/download_storage_service.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
/// In-test fake PathProviderPlatform that points all directories at a real
/// on-disk temp folder. Required because the production service calls
/// [getApplicationDocumentsDirectory] / [getApplicationSupportDirectory] —
/// both of which fail outside an app context unless the platform interface
/// is mocked.
class _FakePathProvider extends PathProviderPlatform with MockPlatformInterfaceMixin {
_FakePathProvider(this.root);
final Directory root;
String get _docs => p.join(root.path, 'documents');
String get _support => p.join(root.path, 'support');
String get _cache => p.join(root.path, 'cache');
String get _temp => p.join(root.path, 'temp');
@override
Future<String?> getApplicationDocumentsPath() async => _ensure(_docs);
@override
Future<String?> getApplicationSupportPath() async => _ensure(_support);
@override
Future<String?> getApplicationCachePath() async => _ensure(_cache);
@override
Future<String?> getTemporaryPath() async => _ensure(_temp);
String _ensure(String dir) {
Directory(dir).createSync(recursive: true);
return dir;
}
}
void main() {
late Directory tmpRoot;
@@ -53,7 +21,7 @@ void main() {
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
tmpRoot = await Directory.systemTemp.createTemp('dss_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
});
tearDown(() async {
@@ -12,6 +12,8 @@ import 'package:plezy/services/jellyfin_endpoint_discovery.dart';
import 'package:plezy/utils/log_redaction_manager.dart';
import 'package:plezy/utils/media_server_timeouts.dart';
import '../test_helpers/backend_client_fixtures.dart';
/// Helpers for stubbing http responses keyed by request path.
typedef _Handler = FutureOr<http.Response> Function(http.BaseRequest req);
@@ -20,12 +22,7 @@ http.Response _bareOk(String body) => http.Response(body, 200, headers: {'conten
http.Response _status(int code, [Object? json]) =>
http.Response(json == null ? '' : jsonEncode(json), code, headers: {'content-type': 'application/json'});
JellyfinConnection _existingConn({String accessToken = 'tok-old'}) => JellyfinConnection(
id: 'srv-1/user-1',
baseUrl: 'https://jf.example.com',
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
JellyfinConnection _existingConn({String accessToken = 'tok-old'}) => testJellyfinConnection(
userName: 'edde',
accessToken: accessToken,
deviceId: 'dev-xyz',
@@ -11,20 +11,18 @@ import 'package:plezy/exceptions/media_server_exceptions.dart';
import 'package:plezy/services/jellyfin_api_cache.dart';
import 'package:plezy/services/jellyfin_client.dart';
JellyfinConnection _conn({String baseUrl = 'https://jf.example.com', List<String>? baseUrls}) => JellyfinConnection(
id: 'srv-1/user-1',
import '../test_helpers/backend_client_fixtures.dart';
JellyfinConnection _conn({String baseUrl = 'https://jf.example.com', List<String>? baseUrls}) => testJellyfinConnection(
baseUrl: baseUrl,
baseUrls: baseUrls,
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
userName: 'edde',
accessToken: 'tok-abc',
deviceId: 'dev-xyz',
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
);
JellyfinClient _withMock(MockClient mock) => JellyfinClient.forTesting(connection: _conn(), httpClient: mock);
JellyfinClient _withMock(MockClient mock) => testJellyfinClient(connection: _conn(), httpClient: mock);
/// Failure-path coverage for the Jellyfin HTTP layer.
///
+8 -6
View File
@@ -14,13 +14,12 @@ import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/playback_initialization_types.dart';
import 'package:plezy/utils/device_identity.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/paged_fakes.dart';
JellyfinConnection _conn({String accessToken = 'tok-abc', String baseUrl = 'https://jf.example.com'}) =>
JellyfinConnection(
id: 'srv-1/user-1',
testJellyfinConnection(
baseUrl: baseUrl,
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
userName: 'edde',
accessToken: accessToken,
deviceId: 'dev-xyz',
@@ -3282,7 +3281,10 @@ void main() {
final start = int.parse(req.url.queryParameters['StartIndex'] ?? '0');
final limit = int.parse(req.url.queryParameters['Limit'] ?? '2');
return http.Response(
jsonEncode({'Items': allItems.skip(start).take(limit).toList(), 'TotalRecordCount': allItems.length}),
jsonEncode({
'Items': sliceFakePage(allItems, start: start, size: limit),
'TotalRecordCount': allItems.length,
}),
200,
headers: {'content-type': 'application/json'},
);
@@ -7,25 +7,23 @@ import 'package:plezy/models/livetv_channel.dart';
import 'package:plezy/services/jellyfin_client.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
JellyfinConnection _conn({required String userId}) => JellyfinConnection(
id: 'srv-shared/$userId',
baseUrl: 'https://jf.example.com',
serverName: 'Shared JF',
serverMachineId: 'srv-shared',
JellyfinConnection _conn({required String userId}) => testJellyfinConnection(
machineId: 'srv-shared',
userId: userId,
serverName: 'Shared JF',
userName: 'user-$userId',
accessToken: 'tok-$userId',
deviceId: 'dev-$userId',
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
);
JellyfinClient _client(JellyfinConnection conn) => JellyfinClient.forTesting(
JellyfinClient _client(JellyfinConnection conn) => testJellyfinClient(
connection: conn,
// Favorites read path is local-only; an http stub that always 500s is
// fine since fetchFavoriteChannels never hits it.
httpClient: MockClient((_) async => throw StateError('no HTTP expected')),
// Favorites read path is local-only; any HTTP call is a test failure.
handler: (_) async => throw StateError('no HTTP expected'),
);
String _favKey(JellyfinConnection conn) => 'jellyfin_fav_channels:${conn.id}';
@@ -9,6 +9,8 @@ import 'package:plezy/media/media_kind.dart';
import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/jellyfin_mappers.dart';
import '../test_helpers/backend_client_fixtures.dart';
const _serverId = 'jf-machine-1';
/// Captured (trimmed) from a live Jellyfin 10.11 server — an `Audio` row
@@ -65,12 +67,7 @@ Map<String, dynamic> _albumJson() => {
'MediaType': 'Unknown',
};
JellyfinConnection _conn() => JellyfinConnection(
id: 'srv-1/user-1',
baseUrl: 'https://jf.example.com',
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
JellyfinConnection _conn() => testJellyfinConnection(
userName: 'edde',
accessToken: 'tok-abc',
deviceId: 'dev-xyz',
@@ -10,12 +10,9 @@ import 'package:plezy/services/jellyfin_api_cache.dart';
import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/plex_api_cache.dart';
JellyfinConnection _conn() => JellyfinConnection(
id: 'srv-1/user-1',
baseUrl: 'https://jf.example.com',
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
import '../test_helpers/backend_client_fixtures.dart';
JellyfinConnection _conn() => testJellyfinConnection(
userName: 'edde',
accessToken: 'tok-abc',
deviceId: 'dev-xyz',
@@ -44,10 +41,10 @@ void main() {
});
JellyfinClient buildClient(String body) {
final mock = MockClient((req) async {
return http.Response(body, 200, headers: {'content-type': 'application/json'});
});
return JellyfinClient.forTesting(connection: _conn(), httpClient: mock);
return testJellyfinClient(
connection: _conn(),
handler: (_) async => http.Response(body, 200, headers: {'content-type': 'application/json'}),
);
}
group('JellyfinClient.fetchPlaybackBundle', () {
@@ -13,6 +13,8 @@ import 'package:plezy/services/media_list_playback_launcher.dart';
import 'package:plezy/services/playlist_items_loader.dart';
import 'package:plezy/utils/media_server_http_client.dart';
import '../test_helpers/paged_fakes.dart';
/// Recording fake that satisfies [JellyfinClient] via `implements` +
/// `noSuchMethod`. The launcher only needs the
/// [MediaServerClient.fetchPlayableDescendants] /
@@ -62,17 +64,9 @@ class _RecordingJellyfinClient implements JellyfinClient {
@override
Future<LibraryPage<MediaItem>> fetchPlaylistPage(String id, {int? start, int? size, AbortController? abort}) async {
final offset = start ?? 0;
final limit = size ?? 100;
final limit = size ?? fakeMediaPageSize;
fetchPlaylistItemsCalls.add((id: id, offset: offset, limit: limit));
if (offset >= playlistItemsResponse.length) {
return LibraryPage<MediaItem>(items: const [], totalCount: playlistItemsResponse.length, offset: offset);
}
final end = (offset + limit).clamp(0, playlistItemsResponse.length);
return LibraryPage<MediaItem>(
items: playlistItemsResponse.sublist(offset, end),
totalCount: playlistItemsResponse.length,
offset: offset,
);
return fakeLibraryPage(playlistItemsResponse, start: start, size: size);
}
@override
@@ -10,12 +10,9 @@ import 'package:plezy/services/jellyfin_trickplay_service.dart';
import 'package:plezy/services/scrub_preview_source.dart';
import 'package:plezy/utils/device_identity.dart';
JellyfinConnection _conn() => JellyfinConnection(
id: 'srv-1/user-1',
baseUrl: 'https://jf.example.com',
serverName: 'Home',
serverMachineId: 'srv-1',
userId: 'user-1',
import '../test_helpers/backend_client_fixtures.dart';
JellyfinConnection _conn() => testJellyfinConnection(
userName: 'edde',
accessToken: 'tok-abc',
deviceId: 'dev-xyz',
@@ -8,7 +8,6 @@ import 'package:plezy/connection/connection.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/ids.dart';
import 'package:plezy/media/media_server_client.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/providers/multi_server_provider.dart';
import 'package:plezy/services/data_aggregation_service.dart';
import 'package:plezy/services/jellyfin_client.dart';
@@ -16,6 +15,8 @@ import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
void main() {
late AppDatabase db;
@@ -26,29 +27,19 @@ void main() {
tearDown(() => db.close());
PlexClient plexClient(http.Client httpClient) => PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'plex-token',
clientIdentifier: 'plex-device',
product: 'Plezy',
version: '1',
machineIdentifier: 'plex-machine',
),
PlexClient plexClient(http.Client httpClient) => testPlexClient(
config: testPlexConfig(token: 'plex-token', clientIdentifier: 'plex-device', machineIdentifier: 'plex-machine'),
serverId: ServerId('plex-machine'),
httpClient: httpClient,
);
JellyfinClient jellyfinClient(http.Client httpClient) => JellyfinClient.forTesting(
connection: JellyfinConnection(
id: 'jellyfin-machine/user-1',
JellyfinClient jellyfinClient(http.Client httpClient) => testJellyfinClient(
connection: testJellyfinConnection(
machineId: 'jellyfin-machine',
userId: 'user-1',
baseUrl: 'https://jellyfin.example.com',
serverName: 'Jellyfin',
serverMachineId: 'jellyfin-machine',
userId: 'user-1',
userName: 'User',
accessToken: 'jellyfin-token',
deviceId: 'device-1',
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
),
httpClient: httpClient,
+5 -9
View File
@@ -14,24 +14,20 @@ import 'package:plezy/services/plex_client.dart';
import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/multi_server_manager.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
JellyfinConnection _jellyfinConnection(String userId) => JellyfinConnection(
id: 'jf-machine/$userId',
baseUrl: 'https://jf.example.com',
serverName: 'Shared JF',
serverMachineId: 'jf-machine',
JellyfinConnection _jellyfinConnection(String userId) => testJellyfinConnection(
machineId: 'jf-machine',
userId: userId,
serverName: 'Shared JF',
userName: userId,
accessToken: 'token-$userId',
deviceId: 'device',
createdAt: DateTime.fromMillisecondsSinceEpoch(0),
);
JellyfinClient _jellyfinClient(String userId) => JellyfinClient.forTesting(
connection: _jellyfinConnection(userId),
httpClient: MockClient((_) async => http.Response('{}', 200)),
);
JellyfinClient _jellyfinClient(String userId) => testJellyfinClient(connection: _jellyfinConnection(userId));
// NOTE on coverage scope:
// [MultiServerManager.addServer] / `connectToAllServers` / `_createClientForServer`
@@ -19,6 +19,7 @@ import 'package:plezy/services/offline_mode_source.dart';
import 'package:plezy/services/offline_watch_sync_service.dart';
import 'package:plezy/utils/watch_state_notifier.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
// NOTE on coverage scope:
@@ -154,12 +155,10 @@ class _ScopedRecordingMediaClient extends _RecordingMediaClient implements Scope
return (svc: svc, db: db, mgr: mgr);
}
JellyfinConnection _jellyfinConnection(String userId) => JellyfinConnection(
id: 'jf-machine/$userId',
baseUrl: 'https://jf.example.com',
serverName: 'Shared JF',
serverMachineId: 'jf-machine',
JellyfinConnection _jellyfinConnection(String userId) => testJellyfinConnection(
machineId: 'jf-machine',
userId: userId,
serverName: 'Shared JF',
userName: userId,
accessToken: 'token-$userId',
deviceId: 'device',
@@ -5,7 +5,6 @@ import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/media_backend.dart';
@@ -21,37 +20,10 @@ import 'package:plezy/services/playback_initialization_service.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_mappers.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
class _FakePathProvider extends PathProviderPlatform with MockPlatformInterfaceMixin {
_FakePathProvider(this.root);
final Directory root;
String get _docs => p.join(root.path, 'documents');
String get _support => p.join(root.path, 'support');
String get _cache => p.join(root.path, 'cache');
String get _temp => p.join(root.path, 'temp');
@override
Future<String?> getApplicationDocumentsPath() async => _ensure(_docs);
@override
Future<String?> getApplicationSupportPath() async => _ensure(_support);
@override
Future<String?> getApplicationCachePath() async => _ensure(_cache);
@override
Future<String?> getTemporaryPath() async => _ensure(_temp);
String _ensure(String dir) {
Directory(dir).createSync(recursive: true);
return dir;
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -63,7 +35,7 @@ void main() {
SettingsService.resetForTesting();
DownloadStorageService.resetForTesting();
tmpRoot = await Directory.systemTemp.createTemp('playback_init_test_');
PathProviderPlatform.instance = _FakePathProvider(tmpRoot);
PathProviderPlatform.instance = FakePathProvider(tmpRoot);
db = AppDatabase.forTesting(NativeDatabase.memory());
PlexApiCache.initialize(db);
JellyfinApiCache.initialize(db);
@@ -3,14 +3,14 @@ import 'dart:convert';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/exceptions/media_server_exceptions.dart';
import 'package:plezy/media/ids.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
void main() {
late AppDatabase db;
@@ -21,19 +21,8 @@ void main() {
tearDown(() => db.close());
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: '1',
),
serverId: ServerId('server-id'),
httpClient: MockClient(handler),
);
}
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) =>
testPlexClient(serverId: ServerId('server-id'), handler: handler);
test('void mutations surface non-success responses', () async {
final client = makeClient((_) async => http.Response('rejected', 500));
+4 -15
View File
@@ -4,13 +4,13 @@ import 'package:plezy/media/ids.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/library_query.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
void main() {
late AppDatabase db;
@@ -23,19 +23,8 @@ void main() {
await db.close();
});
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: '1',
),
serverId: ServerId('server-id'),
httpClient: MockClient(handler),
);
}
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) =>
testPlexClient(serverId: ServerId('server-id'), handler: handler);
test('filters and sorts use dedicated Plex endpoints', () async {
final requests = <Uri>[];
+4 -15
View File
@@ -1,14 +1,14 @@
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/ids.dart';
import 'package:plezy/models/audio_quality_preset.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
void main() {
late AppDatabase db;
@@ -21,19 +21,8 @@ void main() {
await db.close();
});
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: '1',
),
serverId: ServerId('server-id'),
httpClient: MockClient(handler),
);
}
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) =>
testPlexClient(serverId: ServerId('server-id'), handler: handler);
test('music transcode params cap bitrate and carry the musicProfile target', () {
final client = makeClient((_) async => http.Response('not used', 500));
@@ -4,19 +4,19 @@ import 'package:plezy/media/ids.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/media_backend.dart';
import 'package:plezy/media/media_item.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/media/media_source_info.dart';
import 'package:plezy/mpv/mpv.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/models/transcode_quality_preset.dart';
import 'package:plezy/services/playback_initialization_types.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
void main() {
late AppDatabase db;
@@ -29,19 +29,8 @@ void main() {
await db.close();
});
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: '1',
),
serverId: ServerId('server-id'),
httpClient: MockClient(handler),
);
}
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) =>
testPlexClient(serverId: ServerId('server-id'), handler: handler);
MediaSourceInfo mediaInfoWithSubtitles(List<MediaSubtitleTrack> subtitleTracks) {
return MediaSourceInfo(
+4 -16
View File
@@ -4,12 +4,12 @@ import 'package:plezy/media/ids.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
http.Response _json(Object body) => http.Response(jsonEncode(body), 200, headers: {'content-type': 'application/json'});
void main() {
@@ -24,20 +24,8 @@ void main() {
await db.close();
});
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: 'test',
),
serverId: ServerId('plex-1'),
serverName: 'Plex',
httpClient: MockClient(handler),
);
}
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) =>
testPlexClient(serverId: ServerId('plex-1'), serverName: 'Plex', handler: handler);
test('search defaults to 100 movie, TV, and music candidates', () async {
final captured = <Uri>[];
+4 -15
View File
@@ -3,18 +3,18 @@ import 'dart:convert';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/ids.dart';
import 'package:plezy/media/media_backend.dart';
import 'package:plezy/media/media_item.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/models/transcode_quality_preset.dart';
import 'package:plezy/services/playback_initialization_types.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
/// Regression coverage for the Plex transcode reporting bug: while
/// transcoding, the `/:/timeline` reports must carry the playback's
/// `X-Plex-Session-Identifier` so the server correlates the timeline with the
@@ -32,19 +32,8 @@ void main() {
await db.close();
});
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: '1',
),
serverId: ServerId('server-id'),
httpClient: MockClient(handler),
);
}
PlexClient makeClient(Future<http.Response> Function(http.Request request) handler) =>
testPlexClient(serverId: ServerId('server-id'), handler: handler);
/// Captures every request and answers `/:/timeline` with 200 so
/// [PlexClient.updateProgress]'s `throwIfHttpError` is satisfied.
@@ -5,12 +5,12 @@ import 'dart:io';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
void main() {
late AppDatabase db;
@@ -77,23 +77,16 @@ void main() {
}
PlexClient _makeClient(Map<String, dynamic> rootContainer) {
return PlexClient.forTesting(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: 'test',
),
return testPlexClient(
serverId: ServerId('server-id'),
httpClient: MockClient((request) async {
handler: (request) async {
expect(request.url.path, '/');
return http.Response(
jsonEncode({'MediaContainer': rootContainer}),
200,
headers: {'content-type': 'application/json'},
);
}),
},
);
}
+4 -5
View File
@@ -17,14 +17,13 @@ import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/sync_rule_executor.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
JellyfinConnection _jellyfinConnection(String userId) => JellyfinConnection(
id: 'jf-machine/$userId',
baseUrl: 'https://jf.example.com',
serverName: 'Shared JF',
serverMachineId: 'jf-machine',
JellyfinConnection _jellyfinConnection(String userId) => testJellyfinConnection(
machineId: 'jf-machine',
userId: userId,
serverName: 'Shared JF',
userName: userId,
accessToken: 'token-$userId',
deviceId: 'device',