Files
SpotiFLAC-Mobile/test/extension_verification_browser_test.dart
T

138 lines
4.6 KiB
Dart

import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/providers/track_provider.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/utils/extension_auth_launcher.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('plugins.flutter.io/url_launcher');
const backend = MethodChannel('com.zarz.spotiflac/backend');
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
final uri = Uri.parse('https://example.invalid/verify?state=sample');
tearDown(() {
messenger.setMockMethodCallHandler(channel, null);
messenger.setMockMethodCallHandler(backend, null);
});
for (final inAppFirst in [true, false]) {
final browserMode = inAppFirst ? 'in_app_first' : 'external_first';
for (final throwsError in [true, false]) {
test(
'browser fallback after launch failure: $browserMode, throws=$throwsError',
() async {
final modes = <bool>[];
messenger.setMockMethodCallHandler(channel, (call) async {
expect(call.method, 'launch');
final arguments = Map<String, dynamic>.from(call.arguments as Map);
expect(arguments['url'], uri.toString());
modes.add(arguments['useWebView'] as bool);
if (modes.length == 1) {
if (throwsError) {
throw PlatformException(code: 'ACTIVITY_NOT_FOUND');
}
return false;
}
return true;
});
expect(
await launchExtensionAuthUrl(uri, browserMode: browserMode),
isTrue,
);
expect(modes, [inAppFirst, !inAppFirst]);
},
);
}
}
test(
'both browser failures return false so manual help can be shown',
() async {
var launches = 0;
messenger.setMockMethodCallHandler(channel, (_) async {
launches++;
throw PlatformException(code: 'ACTIVITY_NOT_FOUND');
});
expect(
await launchExtensionAuthUrl(uri, browserMode: 'in_app_first'),
isFalse,
);
expect(launches, 2);
},
);
test(
'direct search verifies through the fallback browser and retries once',
() async {
var searches = 0;
var pendingLookups = 0;
var launches = 0;
messenger.setMockMethodCallHandler(backend, (call) async {
switch (call.method) {
case 'customSearchWithExtension':
searches++;
if (searches == 1) {
throw PlatformException(
code: 'ERROR',
message:
"verification_required: extension 'sample-provider' needs signed-session verification: Error: VERIFY_REQUIRED",
);
}
return [
{
'id': 'sample-track',
'name': 'Sample track',
'artist_name': 'Artist',
},
];
case 'getExtensionPendingAuth':
pendingLookups++;
expect(call.arguments, {'extension_id': 'sample-provider'});
return {'auth_url': uri.toString()};
case 'completeExtensionSessionGrant':
return true;
default:
fail('Unexpected backend call: ${call.method}');
}
});
messenger.setMockMethodCallHandler(channel, (call) async {
launches++;
if (launches == 1) throw PlatformException(code: 'ACTIVITY_NOT_FOUND');
unawaited(
PlatformBridge.completeExtensionSessionGrant(
'sample-provider',
'test-grant',
),
);
return true;
});
final container = ProviderContainer(
overrides: [settingsProvider.overrideWith(_Settings.new)],
);
addTearDown(container.dispose);
await container
.read(trackProvider.notifier)
.customSearch('sample-provider', 'fallback verification');
final state = container.read(trackProvider);
expect(state.error, isNull);
expect(state.isLoading, isFalse);
expect(state.tracks.single.id, 'sample-track');
expect(searches, 2);
expect(pendingLookups, 1);
expect(launches, 2);
},
);
}
class _Settings extends SettingsNotifier {
@override
AppSettings build() => const AppSettings();
}