blob: 675433c2fe432588c4439ab8e0a1626347f48812 [file] [log] [blame]
// Copyright 2020 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:file/src/backends/memory/memory_file_system.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'package:device_doctor/src/device.dart';
import 'package:device_doctor/src/health.dart';
import 'package:device_doctor/src/ios_device.dart';
import 'package:device_doctor/src/utils.dart';
import 'fake_ios_device.dart';
import 'utils.dart';
void main() {
group('IosDeviceDiscovery', () {
late FakeIosDeviceDiscovery deviceDiscovery;
late MockProcessManager processManager;
Process process;
setUp(() {
deviceDiscovery = FakeIosDeviceDiscovery(MemoryFileSystem().file('output'));
processManager = MockProcessManager();
});
test('deviceDiscovery', () async {
deviceDiscovery.outputs = <dynamic>[''];
expect(await deviceDiscovery.discoverDevices(), isEmpty);
final StringBuffer sb = StringBuffer();
sb.writeln('abcdefg');
deviceDiscovery.outputs = <dynamic>[sb.toString()];
final List<Device> devices = await deviceDiscovery.discoverDevices();
expect(devices.length, equals(1));
expect(devices[0].deviceId, equals('abcdefg'));
});
test('checkDevices without device', () async {
deviceDiscovery.outputs = <dynamic>[''];
final Map<String, List<HealthCheckResult>> results = await deviceDiscovery.checkDevices();
await expectLater(results.keys.length, 0);
});
test('checkDevices with device', () async {
process = FakeProcess(0, out: <List<int>>[]);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
deviceDiscovery.outputs = <dynamic>['abcdefg'];
final Map<String, List<HealthCheckResult>> results =
await deviceDiscovery.checkDevices(processManager: processManager);
expect(results['ios-device-abcdefg'], isNotNull);
expect(results.keys.length, equals(1));
expect(results.keys.toList()[0], 'ios-device-abcdefg');
final List<HealthCheckResult> healthCheckResults = results['ios-device-abcdefg']!;
expect(healthCheckResults.length, 7);
expect(healthCheckResults[0].name, kDeviceAccessCheckKey);
expect(healthCheckResults[0].succeeded, true);
expect(healthCheckResults[1].name, kKeychainUnlockCheckKey);
expect(healthCheckResults[1].succeeded, true);
expect(healthCheckResults[2].name, kCertCheckKey);
expect(healthCheckResults[2].succeeded, false);
expect(healthCheckResults[3].name, kDevicePairCheckKey);
expect(healthCheckResults[3].succeeded, false);
expect(healthCheckResults[4].name, kUserAutoLoginCheckKey);
expect(healthCheckResults[4].succeeded, false);
expect(healthCheckResults[5].name, kDeviceProvisioningProfileCheckKey);
expect(healthCheckResults[5].succeeded, false);
expect(healthCheckResults[6].name, kBatteryLevelCheckKey);
expect(healthCheckResults[6].succeeded, false);
});
});
group('IosDeviceDiscovery - health checks', () {
late IosDeviceDiscovery deviceDiscovery;
late MockProcessManager processManager;
Process process;
List<List<int>> output;
setUp(() {
processManager = MockProcessManager();
deviceDiscovery = IosDeviceDiscovery(MemoryFileSystem().file('output'));
});
test('Keychain unlock check - success', () async {
process = FakeProcess(0);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult =
await deviceDiscovery.keychainUnlockCheck(processManager: processManager);
expect(healthCheckResult.succeeded, true);
});
test('Keychain unlock check - exception', () async {
process = FakeProcess(1);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult =
await deviceDiscovery.keychainUnlockCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kKeychainUnlockCheckKey);
expect(healthCheckResult.details, 'Executable ${kUnlockLoginKeychain} failed with exit code 1.');
});
test('Cert check - success', () async {
final StringBuffer sb = StringBuffer();
sb.writeln('1) abcdefg "Apple Development: Flutter Devicelab (hijklmn)"');
sb.writeln('1 valid identities found');
output = <List<int>>[utf8.encode(sb.toString())];
process = FakeProcess(0, out: output);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.certCheck(processManager: processManager);
expect(healthCheckResult.succeeded, true);
});
test('Cert check - failure without target certificate', () async {
final StringBuffer sb = StringBuffer();
sb.writeln('abcdefg');
sb.writeln('hijklmn');
output = <List<int>>[utf8.encode(sb.toString())];
process = FakeProcess(0, out: output);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.certCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kCertCheckKey);
expect(healthCheckResult.details, sb.toString().trim());
});
test('Cert check - failure with multiple certificates', () async {
final StringBuffer sb = StringBuffer();
sb.writeln('1) abcdefg "Apple Development: Flutter Devicelab (hijklmn)"');
sb.writeln('1) opqrst "uvwxyz"');
sb.writeln('2 valid identities found');
output = <List<int>>[utf8.encode(sb.toString())];
process = FakeProcess(0, out: output);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.certCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kCertCheckKey);
expect(healthCheckResult.details, sb.toString().trim());
});
test('Cert check - failure with revoked certificates', () async {
final StringBuffer sb = StringBuffer();
sb.writeln('1) abcdefg "Apple Development: Flutter Devicelab (hijklmn)" (CSSMERR_TP_CERT_REVOKED)');
sb.writeln('1 valid identities found');
output = <List<int>>[utf8.encode(sb.toString())];
process = FakeProcess(0, out: output);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.certCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kCertCheckKey);
expect(healthCheckResult.details, sb.toString().trim());
});
test('Cert check - exception', () async {
process = FakeProcess(1);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.certCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kCertCheckKey);
expect(healthCheckResult.details, 'Executable security failed with exit code 1.');
});
test('Device pair check - success', () async {
final StringBuffer sb = StringBuffer();
sb.writeln('SUCCESS: Validated pairing with device abcdefg-hijklmn');
output = <List<int>>[utf8.encode(sb.toString())];
process = FakeProcess(0, out: output);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.devicePairCheck(processManager: processManager);
expect(healthCheckResult.succeeded, true);
});
test('Device pair check - failure', () async {
final StringBuffer sb = StringBuffer();
sb.writeln('abcdefg');
output = <List<int>>[utf8.encode(sb.toString())];
process = FakeProcess(0, out: output);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.devicePairCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kDevicePairCheckKey);
expect(healthCheckResult.details, sb.toString().trim());
});
test('Device pair check - exception', () async {
process = FakeProcess(1);
when(processManager.start(any, workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult = await deviceDiscovery.devicePairCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kDevicePairCheckKey);
expect(healthCheckResult.details, 'Executable idevicepair failed with exit code 1.');
});
group('Device provisioning profile check', () {
Process lsProcess;
Process securityProcess;
List<List<int>> lsOutput;
List<List<int>> securityOutput;
test('success', () async {
const String fileName = 'abcdefg';
lsOutput = <List<int>>[utf8.encode(fileName)];
lsProcess = FakeProcess(0, out: lsOutput);
const String deviceID = 'deviceId';
const String profileContent = '''<array>
<string>test1</string>
<string>$deviceID</string>
<string>test2</string>
</array>
''';
securityOutput = <List<int>>[utf8.encode(profileContent)];
securityProcess = FakeProcess(0, out: securityOutput);
final String? homeDir = Platform.environment['HOME'];
when(
processManager.start(
<Object>['ls', '$homeDir/Library/MobileDevice/Provisioning\ Profiles'],
workingDirectory: anyNamed('workingDirectory'),
),
).thenAnswer((_) => Future.value(lsProcess));
when(
processManager.start(
<Object>['security', 'cms', '-D', '-i', '$homeDir/Library/MobileDevice/Provisioning\ Profiles/$fileName'],
workingDirectory: anyNamed('workingDirectory'),
),
).thenAnswer((_) => Future.value(securityProcess));
final HealthCheckResult healthCheckResult =
await deviceDiscovery.deviceProvisioningProfileCheck(deviceID, processManager: processManager);
expect(healthCheckResult.succeeded, true);
expect(healthCheckResult.name, kDeviceProvisioningProfileCheckKey);
});
test('deviceId does not exist', () async {
const String fileName = 'abcdefg';
lsOutput = <List<int>>[utf8.encode(fileName)];
lsProcess = FakeProcess(0, out: lsOutput);
const String deviceID = 'deviceId';
const String profileContent = '''<array>
<string>test1</string>
<string>test2</string>
</array>
''';
securityOutput = <List<int>>[utf8.encode(profileContent)];
securityProcess = FakeProcess(0, out: securityOutput);
final String? homeDir = Platform.environment['HOME'];
when(
processManager.start(
<Object>['ls', '$homeDir/Library/MobileDevice/Provisioning\ Profiles'],
workingDirectory: anyNamed('workingDirectory'),
),
).thenAnswer((_) => Future.value(lsProcess));
when(
processManager.start(
<Object>['security', 'cms', '-D', '-i', '$homeDir/Library/MobileDevice/Provisioning\ Profiles/$fileName'],
workingDirectory: anyNamed('workingDirectory'),
),
).thenAnswer((_) => Future.value(securityProcess));
final HealthCheckResult healthCheckResult =
await deviceDiscovery.deviceProvisioningProfileCheck(deviceID, processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kDeviceProvisioningProfileCheckKey);
expect(healthCheckResult.details, 'device does not exist in the provisioning profile');
});
});
group('Device battery check', () {
Process process;
List<List<int>> output;
test('battery level is okay', () async {
const String batteryLevel = '100';
output = <List<int>>[utf8.encode(batteryLevel)];
process = FakeProcess(0, out: output);
when(
processManager.start(
<Object>['ideviceinfo', '-q', 'com.apple.mobile.battery', '-k', 'BatteryCurrentCapacity'],
workingDirectory: anyNamed('workingDirectory'),
),
).thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult =
await deviceDiscovery.batteryLevelCheck(processManager: processManager);
expect(healthCheckResult.succeeded, true);
expect(healthCheckResult.name, kBatteryLevelCheckKey);
});
test('battery level is below minLevel', () async {
const String batteryLevel = '10';
output = <List<int>>[utf8.encode(batteryLevel)];
process = FakeProcess(0, out: output);
when(
processManager.start(
<Object>['ideviceinfo', '-q', 'com.apple.mobile.battery', '-k', 'BatteryCurrentCapacity'],
workingDirectory: anyNamed('workingDirectory'),
),
).thenAnswer((_) => Future.value(process));
final HealthCheckResult healthCheckResult =
await deviceDiscovery.batteryLevelCheck(processManager: processManager);
expect(healthCheckResult.succeeded, false);
expect(healthCheckResult.name, kBatteryLevelCheckKey);
expect(healthCheckResult.details, 'Battery level ($batteryLevel) is below 15');
});
});
});
group('IosDevice recovery checks', () {
IosDevice device;
late MockProcessManager processManager;
Process process;
Process whichProcess;
String output;
String ideviceinstallerPath;
String idevicediagnosticsPath;
setUp(() {
processManager = MockProcessManager();
});
test('device restart - success', () async {
idevicediagnosticsPath = '/abc/def/idevicediagnostics';
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(idevicediagnosticsPath)]);
when(
processManager.start(<String>['which', 'idevicediagnostics'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(whichProcess));
process = FakeProcess(0);
device = IosDevice(deviceId: 'abc');
when(
processManager
.start(<Object>[idevicediagnosticsPath, 'restart'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(process));
final bool result = await device.restart_device(processManager: processManager);
expect(result, isTrue);
});
test('device restart - failure', () async {
idevicediagnosticsPath = '/abc/def/idevicediagnostics';
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(idevicediagnosticsPath)]);
when(
processManager.start(<String>['which', 'idevicediagnostics'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(whichProcess));
process = FakeProcess(1);
device = IosDevice(deviceId: 'abc');
when(
processManager
.start(<Object>[idevicediagnosticsPath, 'restart'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(process));
final bool result = await device.restart_device(processManager: processManager);
expect(result, isFalse);
});
test('device restart - skip 32 bit phone', () async {
device = IosDevice(deviceId: '822ef7958bba573829d85eef4df6cbdd86593730');
final bool result = await device.restart_device(processManager: processManager);
expect(result, isTrue);
});
test('list applications - failure', () async {
device = IosDevice(deviceId: '822ef7958bba573829d85eef4df6cbdd86593730');
process = FakeProcess(1);
ideviceinstallerPath = '/abc/def/ideviceinstaller';
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(ideviceinstallerPath)]);
when(processManager.start(<String>['which', 'ideviceinstaller'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(whichProcess));
when(processManager.start(<Object>[ideviceinstallerPath, '-l'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final bool result = await device.uninstall_applications(processManager: processManager);
expect(result, isFalse);
});
test('uninstall applications - no device is available', () async {
ideviceinstallerPath = '/abc/def/ideviceinstaller';
device = IosDevice(deviceId: '822ef7958bba573829d85eef4df6cbdd86593730');
output = '''No device found.
''';
process = FakeProcess(0, out: <List<int>>[utf8.encode(output)]);
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(ideviceinstallerPath)]);
when(processManager.start(<String>['which', 'ideviceinstaller'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(whichProcess));
when(processManager.start(<Object>[ideviceinstallerPath, '-l'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final bool result = await device.uninstall_applications(processManager: processManager);
expect(result, isTrue);
});
test('uninstall applications - no application exist', () async {
ideviceinstallerPath = '/abc/def/ideviceinstaller';
device = IosDevice(deviceId: '822ef7958bba573829d85eef4df6cbdd86593730');
output = '''CFBundleIdentifier, CFBundleVersion, CFBundleDisplayName
''';
process = FakeProcess(0, out: <List<int>>[utf8.encode(output)]);
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(ideviceinstallerPath)]);
when(processManager.start(<String>['which', 'ideviceinstaller'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(whichProcess));
when(processManager.start(<Object>[ideviceinstallerPath, '-l'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
final bool result = await device.uninstall_applications(processManager: processManager);
expect(result, isTrue);
});
test('uninstall applications - applications exist with exception', () async {
ideviceinstallerPath = '/abc/def/ideviceinstaller';
device = IosDevice(deviceId: '822ef7958bba573829d85eef4df6cbdd86593730');
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(ideviceinstallerPath)]);
output = '''CFBundleIdentifier, CFBundleVersion, CFBundleDisplayName
abc, def, ghi
jkl, mno, pqr
''';
process = FakeProcess(0, out: <List<int>>[utf8.encode(output)]);
Process process_uninstall = FakeProcess(1);
when(processManager.start(<String>['which', 'ideviceinstaller'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(whichProcess));
when(processManager.start(<Object>[ideviceinstallerPath, '-l'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
when(
processManager
.start(<Object>[ideviceinstallerPath, '-U', 'abc'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(process_uninstall));
when(
processManager
.start(<Object>[ideviceinstallerPath, '-U', 'jkl'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(process_uninstall));
output = '''CFBundleIdentifier, CFBundleVersion, CFBundleDisplayName
abc, def, ghi
jkl, mno, pqr
''';
process = FakeProcess(0, out: <List<int>>[utf8.encode(output)]);
process_uninstall = FakeProcess(1);
final bool result = await device.uninstall_applications(processManager: processManager);
expect(result, isFalse);
});
test('uninstall applications - applications exist', () async {
ideviceinstallerPath = '/abc/def/ideviceinstaller';
device = IosDevice(deviceId: '822ef7958bba573829d85eef4df6cbdd86593730');
whichProcess = FakeProcess(0, out: <List<int>>[utf8.encode(ideviceinstallerPath)]);
output = '''CFBundleIdentifier, CFBundleVersion, CFBundleDisplayName
abc, def, ghi
jkl, mno, pqr
''';
process = FakeProcess(0, out: <List<int>>[utf8.encode(output)]);
final Process process_uninstall = FakeProcess(0);
when(processManager.start(<String>['which', 'ideviceinstaller'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(whichProcess));
when(processManager.start(<Object>[ideviceinstallerPath, '-l'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(process));
when(
processManager
.start(<Object>[ideviceinstallerPath, '-U', 'abc'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(process_uninstall));
when(
processManager
.start(<Object>[ideviceinstallerPath, '-U', 'jkl'], workingDirectory: anyNamed('workingDirectory')),
).thenAnswer((_) => Future.value(process_uninstall));
final bool result = await device.uninstall_applications(processManager: processManager);
expect(result, isTrue);
});
});
group('deviceListOutput', () {
late IosDeviceDiscovery deviceDiscovery;
late MockProcessManager processManager;
Process processIdeviceID;
Process processWhichIdeviceID;
setUp(() {
processManager = MockProcessManager();
deviceDiscovery = IosDeviceDiscovery(MemoryFileSystem().file('output'));
});
test('success', () async {
processIdeviceID = FakeProcess(0, out: <List<int>>[utf8.encode('abc')]);
processWhichIdeviceID = FakeProcess(0, out: <List<int>>[utf8.encode('/test/idevice_id')]);
when(processManager.start(<String>['which', 'idevice_id'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(processWhichIdeviceID));
when(processManager.start(<String>['/test/idevice_id', '-l'], workingDirectory: anyNamed('workingDirectory')))
.thenAnswer((_) => Future.value(processIdeviceID));
final String deviceId = await deviceDiscovery.deviceListOutput(processManager: processManager);
expect(deviceId, 'abc');
});
});
}