Files
plezy/test/profiles/active_profile_binder_test.dart
T

165 lines
6.2 KiB
Dart

import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/connection/connection_registry.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/profiles/active_profile_binder.dart';
import 'package:plezy/profiles/active_profile_provider.dart';
import 'package:plezy/profiles/plex_home_service.dart';
import 'package:plezy/profiles/profile.dart';
import 'package:plezy/profiles/profile_connection_registry.dart';
import 'package:plezy/profiles/profile_registry.dart';
import 'package:plezy/providers/multi_server_provider.dart';
import 'package:plezy/services/data_aggregation_service.dart';
import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/storage_service.dart';
import '../test_helpers/prefs.dart';
void main() {
late AppDatabase db;
late ConnectionRegistry connections;
late ProfileConnectionRegistry profileConnections;
late ProfileRegistry profiles;
late PlexHomeService plexHome;
late ActiveProfileProvider activeProfile;
late MultiServerManager manager;
late MultiServerProvider multiServerProvider;
late ActiveProfileBinder binder;
late StorageService storage;
late bool shouldDeferInitialBind;
setUp(() async {
resetSharedPreferencesForTest();
db = AppDatabase.forTesting(NativeDatabase.memory());
connections = ConnectionRegistry(db);
profileConnections = ProfileConnectionRegistry(db);
profiles = ProfileRegistry(db);
storage = await StorageService.getInstance();
plexHome = PlexHomeService(
connections: connections,
profileConnections: profileConnections,
storage: storage,
plexHomeUserFetcher: (_) async => const [],
);
activeProfile = ActiveProfileProvider(
registry: profiles,
plexHome: plexHome,
connections: connections,
storage: storage,
);
manager = MultiServerManager();
multiServerProvider = MultiServerProvider(manager, DataAggregationService(manager));
shouldDeferInitialBind = false;
binder = ActiveProfileBinder(
activeProfile: activeProfile,
connections: connections,
profileConnections: profileConnections,
serverManager: manager,
multiServerProvider: multiServerProvider,
pinPrompt: (_, {String? errorMessage}) async => null,
shouldDeferInitialBind: (_) async => shouldDeferInitialBind,
);
});
tearDown(() async {
binder.dispose();
multiServerProvider.dispose();
await activeProfile.resetForTesting();
activeProfile.dispose();
await plexHome.dispose();
await db.close();
});
Future<Profile> createActiveLocalProfile(String id) async {
final profile = Profile.local(id: id, displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
await profiles.upsert(profile);
await storage.setActiveProfileId(profile.id);
await activeProfile.initialize();
return profile;
}
test('local profile with no connections binds successfully with empty visibility', () async {
final profile = Profile.local(id: 'local-owner', displayName: 'Owner', createdAt: DateTime(2026, 1, 1));
await profiles.upsert(profile);
await storage.setActiveProfileId(profile.id);
await activeProfile.initialize();
await binder.rebindActive();
expect(activeProfile.lastBindingSucceeded, isTrue);
expect(binder.debugLastBoundProfileId, profile.id);
expect(multiServerProvider.serverIds, isEmpty);
});
test('started binder does not loop forever after empty local bind', () async {
final profile = Profile.local(id: 'local-empty', displayName: 'Empty', createdAt: DateTime(2026, 1, 1));
await profiles.upsert(profile);
await storage.setActiveProfileId(profile.id);
await activeProfile.initialize();
var notifications = 0;
activeProfile.addListener(() => notifications++);
binder.start();
await Future<void>.delayed(const Duration(milliseconds: 50));
expect(activeProfile.isBinding, isFalse);
expect(activeProfile.lastBindingSucceeded, isTrue);
expect(binder.debugLastBoundProfileId, profile.id);
expect(notifications, lessThan(8));
});
test('initial bind can be deferred until profile selection', () async {
final profile = await createActiveLocalProfile('local-deferred');
shouldDeferInitialBind = true;
await binder.rebindActive();
expect(activeProfile.lastBindingSucceeded, isTrue);
expect(activeProfile.isBinding, isFalse);
expect(binder.debugLastBoundProfileId, isNull);
expect(binder.consumeUserInitiatedActivation(profile.id), isFalse);
expect(multiServerProvider.serverIds, isEmpty);
});
test('user initiated activation bypasses initial bind defer', () async {
final profile = await createActiveLocalProfile('local-user-initiated');
shouldDeferInitialBind = true;
binder.markUserInitiatedActivation(profile.id);
await binder.rebindActive();
expect(activeProfile.lastBindingSucceeded, isTrue);
expect(binder.debugLastBoundProfileId, profile.id);
expect(binder.consumeUserInitiatedActivation(profile.id), isFalse);
});
group('Plex Home token cache policy', () {
test('cold start uses cached token instead of forcing PIN revalidation', () {
expect(shouldUsePlexHomeTokenCache(preVerified: false, hasBoundOnce: false), isTrue);
});
test('preverified activation uses cache once regardless of setting', () {
expect(shouldUsePlexHomeTokenCache(preVerified: true, hasBoundOnce: false), isTrue);
});
test('user-initiated switches bypass cache after first bind', () {
expect(shouldUsePlexHomeTokenCache(preVerified: false, hasBoundOnce: true), isFalse);
});
test('preverified activation flag is consumed once per profile', () {
expect(binder.consumePlexHomePreVerified('plex-home-x'), isFalse);
binder.markPlexHomePreVerified('plex-home-x');
expect(binder.consumePlexHomePreVerified('plex-home-x'), isTrue);
expect(binder.consumePlexHomePreVerified('plex-home-x'), isFalse);
});
test('preverified activation flag isolates entries per profile id', () {
binder.markPlexHomePreVerified('plex-home-a');
binder.markPlexHomePreVerified('plex-home-b');
expect(binder.consumePlexHomePreVerified('plex-home-b'), isTrue);
expect(binder.consumePlexHomePreVerified('plex-home-a'), isTrue);
});
});
}