Files
SpotiFLAC-Mobile/test/network_storage_screen_test.dart
T

246 lines
9.2 KiB
Dart

import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/screens/network_storage_screen.dart';
import 'package:spotiflac_android/services/network_certificate.dart';
import 'package:spotiflac_android/services/network_storage_service.dart';
import 'package:spotiflac_android/theme/mornye_theme.dart';
import 'package:spotiflac_android/widgets/app_action_button.dart';
void main() {
testWidgets('failed sign-in stays on the form with a specific error', (
tester,
) async {
final service = _NetworkFixture()
..error = const SocketException('private-host');
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: NetworkStorageScreen(service: service),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Add connection'));
await tester.pumpAndSettle();
await tester.enterText(find.byKey(const ValueKey('Name')), 'NAS');
await tester.enterText(
find.byKey(const ValueKey('Server or audio URL')),
'smb://server/share',
);
final save = find.widgetWithText(AppActionButton, 'Connect and save');
await tester.ensureVisible(save);
await tester.pumpAndSettle();
await tester.tap(save);
await tester.pumpAndSettle();
expect(
find.textContaining('The server could not be reached.'),
findsOneWidget,
);
expect(find.textContaining('private-host'), findsNothing);
expect(await service.connections(), isEmpty);
});
for (final mornye in [false, true]) {
testWidgets(
'certificate trust requires a tap and stays on its server (Mornye: $mornye)',
(tester) async {
const certificate = NetworkCertificateException(
origin: 'https://nas.test:6008',
fingerprint: 'fixture-fingerprint',
);
final service = _NetworkFixture()..error = certificate;
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
theme: mornye
? MornyeTheme.build(Brightness.dark)
: ThemeData(useMaterial3: true),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: NetworkStorageScreen(service: service),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Add connection'));
await tester.pumpAndSettle();
await tester.tap(find.text('WebDAV'));
await tester.pumpAndSettle();
await tester.enterText(find.byKey(const ValueKey('Name')), 'NAS');
final address = find.byKey(const ValueKey('Server or audio URL'));
await tester.enterText(address, 'https://nas.test:6008/music');
await tester.pumpAndSettle();
final save = find.widgetWithText(AppActionButton, 'Connect and save');
await tester.ensureVisible(save);
await tester.pumpAndSettle();
await tester.tap(save);
await tester.pumpAndSettle();
expect(await service.connections(), isEmpty);
expect(find.textContaining(certificate.fingerprint), findsOneWidget);
final trust = find.widgetWithText(
AppActionButton,
'Trust certificate and retry',
);
// Editing the server after an error must not transfer the approval.
await tester.ensureVisible(address);
await tester.enterText(address, 'https://other.test:6008/music');
await tester.pumpAndSettle();
await tester.ensureVisible(trust);
await tester.pumpAndSettle();
await tester.tap(trust);
await tester.pumpAndSettle();
expect(service.lastAttempt!.trustedCertificateSha256, isNull);
expect(await service.connections(), isEmpty);
await tester.ensureVisible(address);
await tester.enterText(address, 'https://nas.test:6008/music');
await tester.pumpAndSettle();
await tester.ensureVisible(trust);
await tester.pumpAndSettle();
await tester.tap(trust);
await tester.pumpAndSettle();
expect(
(await service.connections()).single.trustedCertificateSha256,
certificate.fingerprint,
);
expect(tester.takeException(), isNull);
},
);
testWidgets(
'saved networks open their own browser and retry (Mornye: $mornye)',
(tester) async {
final service = _NetworkFixture();
await service.save(
const NetworkConnection(
id: 'nas',
name: 'My NAS',
protocol: NetworkProtocol.webdav,
address: 'https://nas.test/music/',
),
);
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
theme: mornye
? MornyeTheme.build(Brightness.dark)
: ThemeData(useMaterial3: true),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: NetworkStorageScreen(service: service),
),
),
);
await tester.pumpAndSettle();
expect(find.text('My NAS'), findsOneWidget);
await tester.tap(find.text('My NAS'));
await tester.pumpAndSettle();
expect(find.text('Album'), findsOneWidget);
await tester.tap(find.text('Album'));
await tester.pumpAndSettle();
expect(find.text('Song.flac'), findsOneWidget);
expect(service.paths, ['', 'Album/']);
service.fail = true;
await tester.tap(find.byTooltip('Refresh').last);
await tester.pumpAndSettle();
expect(
find.textContaining('Could not access the network source.'),
findsOneWidget,
);
service.fail = false;
await tester.tap(find.widgetWithText(AppActionButton, 'Retry'));
await tester.pumpAndSettle();
expect(find.text('Song.flac'), findsOneWidget);
expect(tester.takeException(), isNull);
},
);
testWidgets(
'connection form saves in the selected theme (Mornye: $mornye)',
(tester) async {
tester.view.physicalSize = const Size(390, 844);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final service = _NetworkFixture();
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
theme: mornye
? MornyeTheme.build(Brightness.light)
: ThemeData(useMaterial3: true),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: NetworkStorageScreen(service: service),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Add connection'));
await tester.pumpAndSettle();
expect(
find.byType(CupertinoTextFormFieldRow),
mornye ? findsWidgets : findsNothing,
);
expect(
find.byType(TextFormField),
mornye ? findsNothing : findsWidgets,
);
await tester.enterText(
find.descendant(
of: find.byKey(const ValueKey('Name')),
matching: find.byType(EditableText),
),
'Home music',
);
await tester.enterText(
find.descendant(
of: find.byKey(const ValueKey('Server or audio URL')),
matching: find.byType(EditableText),
),
'smb://nas.test/Music/',
);
final save = find.widgetWithText(AppActionButton, 'Connect and save');
await tester.ensureVisible(save);
await tester.pumpAndSettle();
await tester.tap(save);
await tester.pumpAndSettle();
expect((await service.connections()).single.name, 'Home music');
expect(find.text('Home music'), findsOneWidget);
expect(tester.takeException(), isNull);
},
);
}
}
class _NetworkFixture extends NetworkStorageService {
_NetworkFixture() : super(read: () async => null, write: (_) async {});
final paths = <String>[];
bool fail = false;
Object? error;
NetworkConnection? lastAttempt;
@override
Future<List<NetworkEntry>> list(
NetworkConnection c, [
String path = '',
]) async {
paths.add(path);
lastAttempt = c;
if (error case final NetworkCertificateException certificate) {
if (c.trustedCertificateSha256 != certificate.fingerprint ||
c.root.origin != certificate.origin) {
throw certificate;
}
} else if (error != null) {
throw error!;
}
if (fail) throw StateError('Offline');
return path.isEmpty
? [const NetworkEntry('Album/', 'Album', directory: true)]
: [const NetworkEntry('Album/Song.flac', 'Song.flac')];
}
}