fix(jellyfin): harden local discovery flow

This commit is contained in:
edde746
2026-05-31 14:33:31 +02:00
parent e3db54940d
commit 1b5334e72f
10 changed files with 397 additions and 81 deletions
@@ -132,6 +132,8 @@ void main() {
await tester.testTextInput.receiveAction(TextInputAction.go);
await tester.pumpAndSettle();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Username');
await tester.tap(find.byType(TextField).first);
await tester.pump();
@@ -191,6 +193,51 @@ void main() {
expect(find.text('Jellyfin 10.9.0'), findsOneWidget);
});
testWidgets('D-pad can navigate through discovered Jellyfin servers', (tester) async {
await tester.pumpWidget(
InputModeTracker(
child: MaterialApp(
home: AddJellyfinScreen(
localDiscoveryFactory: () async => [
DiscoveredJellyfinServer(address: 'http://192.168.1.20:8096', id: 'srv-1', name: 'Home'),
DiscoveredJellyfinServer(address: 'http://192.168.1.30:8096', id: 'srv-2', name: 'Office'),
],
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Home'), findsOneWidget);
expect(find.text('Office'), findsOneWidget);
expect(find.byType(OutlinedButton), findsNothing);
await tester.tap(find.byType(TextField).first);
await tester.pump();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Url');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Discovered:srv-1');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Discovered:srv-2');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:FindServer');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pump();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Discovered:srv-2');
});
group('Jellyfin profile binding decisions', () {
test('creates a local profile only on true first-run with no profiles', () {
expect(shouldCreateLocalJellyfinProfile(targetProfile: null, activeProfile: null, hasProfiles: false), isTrue);
@@ -58,7 +58,7 @@ void main() {
final result = await discovery.raceEndpoints(
input.probeBaseUrls,
baseUrlsToPersist: input.explicitBaseUrls,
baseUrlsToValidate: input.explicitBaseUrls,
baseUrlValidationGroups: input.validationBaseUrlGroups,
);
expect(result.activeBaseUrl, 'http://jf.example.com:8096');
@@ -79,7 +79,7 @@ void main() {
final result = await discovery.raceEndpoints(
input.probeBaseUrls,
baseUrlsToPersist: input.explicitBaseUrls,
baseUrlsToValidate: input.explicitBaseUrls,
baseUrlValidationGroups: input.validationBaseUrlGroups,
);
expect(result.baseUrls, ['http://192.168.1.10:8096']);
@@ -102,12 +102,36 @@ void main() {
final result = await discovery.raceEndpoints(
input.probeBaseUrls,
baseUrlsToPersist: input.explicitBaseUrls,
baseUrlsToValidate: input.explicitBaseUrls,
baseUrlValidationGroups: input.validationBaseUrlGroups,
);
expect(result.baseUrls, [result.activeBaseUrl]);
});
test('rejects different servers reached from separate shorthand entries', () async {
final discovery = JellyfinEndpointDiscovery(
testHttpClientFactory: () => MockClient((req) async {
if (req.url.scheme == 'http' && req.url.host == 'one.example.com' && req.url.port == 8096) {
return _info(id: 'srv-1');
}
if (req.url.scheme == 'http' && req.url.host == 'two.example.com' && req.url.port == 8096) {
return _info(id: 'srv-2');
}
throw TimeoutException('offline');
}),
);
final input = JellyfinEndpointDiscovery.buildUserInputCandidates(['one.example.com', 'two.example.com']);
await expectLater(
discovery.raceEndpoints(
input.probeBaseUrls,
baseUrlsToPersist: input.explicitBaseUrls,
baseUrlValidationGroups: input.validationBaseUrlGroups,
),
throwsA(isA<MediaServerUrlException>()),
);
});
test('retains explicit user-entered failover URLs when using input candidates', () async {
final discovery = JellyfinEndpointDiscovery(
testHttpClientFactory: () => MockClient((req) async {
@@ -125,7 +149,7 @@ void main() {
final result = await discovery.raceEndpoints(
input.probeBaseUrls,
baseUrlsToPersist: input.explicitBaseUrls,
baseUrlsToValidate: input.explicitBaseUrls,
baseUrlValidationGroups: input.validationBaseUrlGroups,
);
expect(result.activeBaseUrl, 'https://jf.example.com');
@@ -31,5 +31,15 @@ void main() {
isNull,
);
});
test('sorts discovered servers deterministically', () {
final sorted = JellyfinLanDiscoveryService.sortDiscoveredServers([
DiscoveredJellyfinServer(address: 'http://192.168.1.20:8096', id: 'srv-2', name: 'Home'),
DiscoveredJellyfinServer(address: 'http://192.168.1.10:8096', id: 'srv-3', name: 'Office'),
DiscoveredJellyfinServer(address: 'http://192.168.1.20:8096', id: 'srv-1', name: 'Home'),
]);
expect(sorted.map((server) => server.id), ['srv-1', 'srv-2', 'srv-3']);
});
});
}