From 5fa88ea0b1cbe2e218ab009fbd10b9e76d7a5ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Sun, 8 Sep 2024 18:30:02 +0200 Subject: [PATCH] feat: move custom commands to separate class --- MiniSim.xcodeproj/project.pbxproj | 12 ++ .../AppleScript Commands/GetCommands.swift | 2 +- MiniSim/Menu.swift | 2 +- MiniSim/Service/Adb.swift | 1 + MiniSim/Service/CustomCommandService.swift | 52 ++++++ MiniSim/Service/DeviceConstants.swift | 16 ++ MiniSim/Service/DeviceService.swift | 92 ++--------- MiniSimTests/CustomCommandServiceTests.swift | 148 ++++++++++++++++++ .../CustomCommandServiceTests.swift.plist | Bin 0 -> 42 bytes MiniSimTests/DeviceParserTests.swift | 8 + MiniSimTests/Mocks/ShellStub.swift | 6 +- 11 files changed, 259 insertions(+), 80 deletions(-) create mode 100644 MiniSim/Service/CustomCommandService.swift create mode 100644 MiniSim/Service/DeviceConstants.swift create mode 100644 MiniSimTests/CustomCommandServiceTests.swift create mode 100644 MiniSimTests/CustomCommandServiceTests.swift.plist diff --git a/MiniSim.xcodeproj/project.pbxproj b/MiniSim.xcodeproj/project.pbxproj index 2e03863..6cc1b2c 100644 --- a/MiniSim.xcodeproj/project.pbxproj +++ b/MiniSim.xcodeproj/project.pbxproj @@ -81,6 +81,9 @@ 76BF0AD92C8CB3E6003BE568 /* AcknowList in Frameworks */ = {isa = PBXBuildFile; productRef = 76BF0AD82C8CB3E6003BE568 /* AcknowList */; }; 76BF0ADB2C8CB4CD003BE568 /* Package.resolved in Resources */ = {isa = PBXBuildFile; fileRef = 76BF0ADA2C8CB4CD003BE568 /* Package.resolved */; }; 76BF0ADD2C8DF660003BE568 /* AccessibilityElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0ADC2C8DF660003BE568 /* AccessibilityElementTests.swift */; }; + 76BF0ADF2C8E01B3003BE568 /* CustomCommandService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0ADE2C8E01B3003BE568 /* CustomCommandService.swift */; }; + 76BF0AE12C8E027D003BE568 /* DeviceConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0AE02C8E027D003BE568 /* DeviceConstants.swift */; }; + 76BF0AE32C8E041C003BE568 /* CustomCommandServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0AE22C8E041C003BE568 /* CustomCommandServiceTests.swift */; }; 76C1396A2C849A3F006CD80C /* MenuIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C139692C849A3F006CD80C /* MenuIcons.swift */; }; 76E4451229D4391000039025 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451129D4391000039025 /* Onboarding.swift */; }; 76E4451429D4403F00039025 /* NSNotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451329D4403F00039025 /* NSNotificationName.swift */; }; @@ -178,6 +181,9 @@ 76B70F832B0D5AB4009D87A4 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = ""; }; 76BF0ADA2C8CB4CD003BE568 /* Package.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Package.resolved; path = MiniSim.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = SOURCE_ROOT; }; 76BF0ADC2C8DF660003BE568 /* AccessibilityElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityElementTests.swift; sourceTree = ""; }; + 76BF0ADE2C8E01B3003BE568 /* CustomCommandService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCommandService.swift; sourceTree = ""; }; + 76BF0AE02C8E027D003BE568 /* DeviceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConstants.swift; sourceTree = ""; }; + 76BF0AE22C8E041C003BE568 /* CustomCommandServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCommandServiceTests.swift; sourceTree = ""; }; 76C139692C849A3F006CD80C /* MenuIcons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuIcons.swift; sourceTree = ""; }; 76E4451129D4391000039025 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; 76E4451329D4403F00039025 /* NSNotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNotificationName.swift; sourceTree = ""; }; @@ -314,6 +320,8 @@ 7699511C2C845B1900462287 /* DeviceParser.swift */, 76F04A10298A5AE000BF9CA3 /* ADB.swift */, 76B70F832B0D5AB4009D87A4 /* Shell.swift */, + 76BF0ADE2C8E01B3003BE568 /* CustomCommandService.swift */, + 76BF0AE02C8E027D003BE568 /* DeviceConstants.swift */, ); path = Service; sourceTree = ""; @@ -418,6 +426,7 @@ 7699511E2C845CBA00462287 /* DeviceParserTests.swift */, 76B70F812B0D50FE009D87A4 /* ADBTests.swift */, 76BF0ADC2C8DF660003BE568 /* AccessibilityElementTests.swift */, + 76BF0AE22C8E041C003BE568 /* CustomCommandServiceTests.swift */, ); path = MiniSimTests; sourceTree = ""; @@ -612,7 +621,9 @@ 76F269852A2A376A00424BDA /* CustomCommandError.swift in Sources */, 767799A029C30BF5009030F8 /* BlurredView.swift in Sources */, 7645D4C42982CB2B00019227 /* MiniSim.swift in Sources */, + 76BF0ADF2C8E01B3003BE568 /* CustomCommandService.swift in Sources */, 76F2A912299033EA002D4EF6 /* DeviceError.swift in Sources */, + 76BF0AE12C8E027D003BE568 /* DeviceConstants.swift in Sources */, 7631218E2A12B3BA00EE7F48 /* CustomCommandsViewModel.swift in Sources */, 4AFACC782AD74E9000EC369F /* DeviceListSection.swift in Sources */, 760554A32C085BEA001607FE /* Thread+Asserts.swift in Sources */, @@ -678,6 +689,7 @@ files = ( 7699511F2C845CBA00462287 /* DeviceParserTests.swift in Sources */, 76BF0ADD2C8DF660003BE568 /* AccessibilityElementTests.swift in Sources */, + 76BF0AE32C8E041C003BE568 /* CustomCommandServiceTests.swift in Sources */, 76B70F7E2B0D361A009D87A4 /* UserDefaultsTests.swift in Sources */, 760DEACE2B0DFB6600253576 /* ShellStub.swift in Sources */, 76B70F822B0D50FE009D87A4 /* ADBTests.swift in Sources */, diff --git a/MiniSim/AppleScript Commands/GetCommands.swift b/MiniSim/AppleScript Commands/GetCommands.swift index 251e598..6efb59e 100644 --- a/MiniSim/AppleScript Commands/GetCommands.swift +++ b/MiniSim/AppleScript Commands/GetCommands.swift @@ -24,7 +24,7 @@ class GetCommands: NSScriptCommand { let commands = SubMenuItems.items(platform: platform, deviceType: deviceType) .compactMap { $0 as? SubMenuActionItem } .map { $0.commandItem } - let customCommands = DeviceService.getCustomCommands(platform: platform) + let customCommands = CustomCommandService.getCustomCommands(platform: platform) .map { command in Command( id: command.id, diff --git a/MiniSim/Menu.swift b/MiniSim/Menu.swift index 7c57164..2c6702d 100644 --- a/MiniSim/Menu.swift +++ b/MiniSim/Menu.swift @@ -284,7 +284,7 @@ class Menu: NSMenu { } func createCustomCommandsMenu(for platform: Platform, isDeviceBooted: Bool, callback: Selector) -> [NSMenuItem] { - DeviceService.getCustomCommands(platform: platform) + CustomCommandService.getCustomCommands(platform: platform) .filter { item in if item.needBootedDevice && !isDeviceBooted { return false diff --git a/MiniSim/Service/Adb.swift b/MiniSim/Service/Adb.swift index 8a66659..5ba89f6 100644 --- a/MiniSim/Service/Adb.swift +++ b/MiniSim/Service/Adb.swift @@ -12,6 +12,7 @@ protocol ADBProtocol { static func getAdbPath() throws -> String static func getEmulatorPath() throws -> String + static func getAndroidHome() throws -> String static func getAdbId(for deviceName: String) throws -> String static func checkAndroidHome( path: String, diff --git a/MiniSim/Service/CustomCommandService.swift b/MiniSim/Service/CustomCommandService.swift new file mode 100644 index 0000000..2f38862 --- /dev/null +++ b/MiniSim/Service/CustomCommandService.swift @@ -0,0 +1,52 @@ +import Foundation + +class CustomCommandService { + static var shell: ShellProtocol = Shell() + static var adb: ADBProtocol.Type = ADB.self + + static func getCustomCommands(platform: Platform, userDefaults: UserDefaults = UserDefaults.standard) -> [Command] { + guard let commandsData = userDefaults.commands else { return [] } + guard let commands = try? JSONDecoder().decode([Command].self, from: commandsData) else { + return [] + } + + return commands.filter { $0.platform == platform } + } + + static func getCustomCommand( + platform: Platform, + commandName: String, + userDefaults: UserDefaults = UserDefaults.standard + ) -> Command? { + let commands = getCustomCommands(platform: platform, userDefaults: userDefaults) + return commands.first { $0.name == commandName } + } + + static func runCustomCommand(_ device: Device, command: Command) throws { + var commandToExecute = command.command + .replacingOccurrences(of: Variables.deviceName.rawValue, with: device.name) + + let deviceID = device.identifier ?? "" + + if command.platform == .android { + commandToExecute = try commandToExecute + .replacingOccurrences(of: Variables.adbPath.rawValue, with: adb.getAdbPath()) + .replacingOccurrences(of: Variables.adbId.rawValue, with: deviceID) + .replacingOccurrences(of: Variables.androidHomePath.rawValue, with: adb.getAndroidHome()) + } else { + commandToExecute = commandToExecute + .replacingOccurrences(of: Variables.uuid.rawValue, with: deviceID) + .replacingOccurrences(of: Variables.xcrunPath.rawValue, with: DeviceConstants.ProcessPaths.xcrun.rawValue) + } + + do { + try shell.execute(command: commandToExecute) + if command.bootsDevice ?? false && command.platform == .ios { + try? DeviceService.launchSimulatorApp(uuid: deviceID) + } + NotificationCenter.default.post(name: .commandDidSucceed, object: nil) + } catch { + throw CustomCommandError.commandError(errorMessage: error.localizedDescription) + } + } +} diff --git a/MiniSim/Service/DeviceConstants.swift b/MiniSim/Service/DeviceConstants.swift new file mode 100644 index 0000000..eaebce5 --- /dev/null +++ b/MiniSim/Service/DeviceConstants.swift @@ -0,0 +1,16 @@ +import Foundation + +enum DeviceConstants { + static let deviceBootedError = "Unable to boot device in current state: Booted" + static let derivedDataLocation = "~/Library/Developer/Xcode/DerivedData" + + enum ProcessPaths: String { + case xcrun = "/usr/bin/xcrun" + case xcodeSelect = "/usr/bin/xcode-select" + } + + enum BundleURL: String { + case emulator = "qemu-system-aarch64" + case simulator = "Simulator.app" + } +} diff --git a/MiniSim/Service/DeviceService.swift b/MiniSim/Service/DeviceService.swift index dba653f..6b9e78f 100644 --- a/MiniSim/Service/DeviceService.swift +++ b/MiniSim/Service/DeviceService.swift @@ -21,9 +21,6 @@ protocol DeviceServiceProtocol { static func checkAndroidSetup() throws -> String static func focusDevice(_ device: Device) - static func runCustomCommand(_ device: Device, command: Command) throws - static func getCustomCommands(platform: Platform) -> [Command] - static func getCustomCommand(platform: Platform, commandName: String) -> Command? static func showSuccessMessage(title: String, message: String) } @@ -33,61 +30,6 @@ class DeviceService: DeviceServiceProtocol { qos: .userInteractive, attributes: .concurrent ) - private static let deviceBootedError = "Unable to boot device in current state: Booted" - private static let derivedDataLocation = "~/Library/Developer/Xcode/DerivedData" - - private enum ProcessPaths: String { - case xcrun = "/usr/bin/xcrun" - case xcodeSelect = "/usr/bin/xcode-select" - } - - private enum BundleURL: String { - case emulator = "qemu-system-aarch64" - case simulator = "Simulator.app" - } - - static func getCustomCommands(platform: Platform) -> [Command] { - guard let commandsData = UserDefaults.standard.commands else { return [] } - guard let commands = try? JSONDecoder().decode([Command].self, from: commandsData) else { - return [] - } - - return commands.filter { $0.platform == platform } - } - - static func getCustomCommand(platform: Platform, commandName: String) -> Command? { - let commands = getCustomCommands(platform: platform) - return commands.first { $0.name == commandName } - } - - static func runCustomCommand(_ device: Device, command: Command) throws { - Thread.assertBackgroundThread() - var commandToExecute = command.command - .replacingOccurrences(of: Variables.deviceName.rawValue, with: device.name) - - let deviceID = device.identifier ?? "" - - if command.platform == .android { - commandToExecute = try commandToExecute - .replacingOccurrences(of: Variables.adbPath.rawValue, with: ADB.getAdbPath()) - .replacingOccurrences(of: Variables.adbId.rawValue, with: deviceID) - .replacingOccurrences(of: Variables.androidHomePath.rawValue, with: ADB.getAndroidHome()) - } else { - commandToExecute = commandToExecute - .replacingOccurrences(of: Variables.uuid.rawValue, with: deviceID) - .replacingOccurrences(of: Variables.xcrunPath.rawValue, with: ProcessPaths.xcrun.rawValue) - } - - do { - try shellOut(to: commandToExecute) - if command.bootsDevice ?? false && command.platform == .ios { - try? launchSimulatorApp(uuid: deviceID) - } - NotificationCenter.default.post(name: .commandDidSucceed, object: nil) - } catch { - throw CustomCommandError.commandError(errorMessage: error.localizedDescription) - } - } static func focusDevice(_ device: Device) { queue.async { @@ -100,11 +42,11 @@ class DeviceService: DeviceServiceProtocol { for app in runningApps { guard let bundleURL = app.bundleURL?.absoluteString, - bundleURL.contains(BundleURL.simulator.rawValue) || - bundleURL.contains(BundleURL.emulator.rawValue) else { + bundleURL.contains(DeviceConstants.BundleURL.simulator.rawValue) || + bundleURL.contains(DeviceConstants.BundleURL.emulator.rawValue) else { continue } - let isAndroid = bundleURL.contains(BundleURL.emulator.rawValue) + let isAndroid = bundleURL.contains(DeviceConstants.BundleURL.emulator.rawValue) for window in AccessibilityElement.allWindowsForPID(app.processIdentifier) { guard let windowTitle = window.attribute(key: .title, type: String.self), @@ -139,7 +81,7 @@ class DeviceService: DeviceServiceProtocol { } static func checkXcodeSetup() -> Bool { - FileManager.default.fileExists(atPath: ProcessPaths.xcrun.rawValue) + FileManager.default.fileExists(atPath: DeviceConstants.ProcessPaths.xcrun.rawValue) } static func checkAndroidSetup() throws -> String { @@ -200,7 +142,7 @@ class DeviceService: DeviceServiceProtocol { completion(nil) } } catch { - if error.localizedDescription.contains(deviceBootedError) { + if error.localizedDescription.contains(DeviceConstants.deviceBootedError) { return } completionQueue.async { @@ -219,9 +161,9 @@ extension DeviceService { ) { self.queue.async { do { - let amountCleared = try? shellOut(to: "du -sh \(derivedDataLocation)") + let amountCleared = try? shellOut(to: "du -sh \(DeviceConstants.derivedDataLocation)") .match(###"\d+\.?\d+\w+"###).first?.first - try shellOut(to: "rm -rf \(derivedDataLocation)") + try shellOut(to: "rm -rf \(DeviceConstants.derivedDataLocation)") completionQueue.async { completion(amountCleared ?? "", nil) } @@ -245,7 +187,7 @@ extension DeviceService { let outputFile = tempDirectory.appendingPathComponent("iosPhysicalDevices.json") guard (try? shellOut( - to: ProcessPaths.xcrun.rawValue, + to: DeviceConstants.ProcessPaths.xcrun.rawValue, arguments: ["devicectl", "list", "devices", "-j \(outputFile.path)"] )) != nil else { return [] @@ -257,7 +199,7 @@ extension DeviceService { static func getIOSSimulators() throws -> [Device] { let output = try shellOut( - to: ProcessPaths.xcrun.rawValue, + to: DeviceConstants.ProcessPaths.xcrun.rawValue, arguments: ["simctl", "list", "devices", "available"] ) return DeviceParserFactory().getParser(.iosSimulator).parse(output) @@ -269,7 +211,7 @@ extension DeviceService { if !isSimulatorRunning { guard let activeDeveloperDir = try? shellOut( - to: ProcessPaths.xcodeSelect.rawValue, + to: DeviceConstants.ProcessPaths.xcodeSelect.rawValue, arguments: ["-p"] ) .trimmingCharacters(in: .whitespacesAndNewlines) else { @@ -285,9 +227,9 @@ extension DeviceService { private static func launchDevice(uuid: String) throws { do { try self.launchSimulatorApp(uuid: uuid) - try shellOut(to: ProcessPaths.xcrun.rawValue, arguments: ["simctl", "boot", uuid]) + try shellOut(to: DeviceConstants.ProcessPaths.xcrun.rawValue, arguments: ["simctl", "boot", uuid]) } catch { - if !error.localizedDescription.contains(deviceBootedError) { + if !error.localizedDescription.contains(DeviceConstants.deviceBootedError) { throw error } } @@ -295,7 +237,7 @@ extension DeviceService { static func deleteSimulator(uuid: String) throws { Thread.assertBackgroundThread() - try shellOut(to: ProcessPaths.xcrun.rawValue, arguments: ["simctl", "delete", uuid]) + try shellOut(to: DeviceConstants.ProcessPaths.xcrun.rawValue, arguments: ["simctl", "delete", uuid]) } static func handleiOSAction(device: Device, commandTag: SubMenuItems.Tags, itemName: String) { @@ -329,12 +271,12 @@ extension DeviceService { } } case .customCommand: - guard let command = DeviceService.getCustomCommand(platform: .ios, commandName: itemName) else { + guard let command = CustomCommandService.getCustomCommand(platform: .ios, commandName: itemName) else { return } do { - try DeviceService.runCustomCommand(device, command: command) + try CustomCommandService.runCustomCommand(device, command: command) } catch { NSAlert.showError(message: error.localizedDescription) } @@ -479,8 +421,8 @@ extension DeviceService { try DeviceService.sendText(device: device, text: text) case .customCommand: - if let command = DeviceService.getCustomCommand(platform: .android, commandName: itemName) { - try DeviceService.runCustomCommand(device, command: command) + if let command = CustomCommandService.getCustomCommand(platform: .android, commandName: itemName) { + try CustomCommandService.runCustomCommand(device, command: command) } case .logcat: try DeviceService.launchLogCat(device: device) diff --git a/MiniSimTests/CustomCommandServiceTests.swift b/MiniSimTests/CustomCommandServiceTests.swift new file mode 100644 index 0000000..a2c808f --- /dev/null +++ b/MiniSimTests/CustomCommandServiceTests.swift @@ -0,0 +1,148 @@ +@testable import MiniSim // Adjust this import to match your actual module name +import XCTest + +class CustomCommandServiceTests: XCTestCase { + class ADB: ADBProtocol { + static var shell: ShellProtocol = Shell() + + static func getAndroidHome() throws -> String { + "mocked_android_home" + } + + static func getAdbId(for deviceName: String) throws -> String { + if deviceName == "Nexus_5X_API_28" { + throw NSError(domain: "ADBError", code: 1, userInfo: nil) + } + return "mock_adb_id_for_\(deviceName)" + } + + static func checkAndroidHome(path: String, fileManager: FileManager) throws -> Bool { + true + } + + static func isAccesibilityOn(deviceId: String) -> Bool { + false + } + + static func toggleAccesibility(deviceId: String) { + } + + static func getEmulatorPath() throws -> String { + "" + } + + static func getAdbPath() throws -> String { + "mocked_adb_path" + } + } + + var userDefaults: UserDefaults! + var shellStub: ShellStub! + + override func setUp() { + super.setUp() + shellStub = ShellStub() + userDefaults = UserDefaults(suiteName: #file) + CustomCommandService.shell = shellStub + CustomCommandService.adb = ADB.self + } + + override func tearDown() { + userDefaults.removeObject(forKey: "commands") + CustomCommandService.shell = Shell() + super.tearDown() + } + + func testGetCustomCommands() { + let commands = [ + Command(name: "Test1", command: "cmd1", icon: "icon1", platform: .ios, needBootedDevice: false), + Command(name: "Test2", command: "cmd2", icon: "icon2", platform: .android, needBootedDevice: true) + ] + let encodedCommands = try? JSONEncoder().encode(commands) + userDefaults.set(encodedCommands, forKey: "commands") + + let iosCommands = CustomCommandService.getCustomCommands(platform: .ios, userDefaults: userDefaults) + let androidCommands = CustomCommandService.getCustomCommands(platform: .android, userDefaults: userDefaults) + + XCTAssertEqual(iosCommands.count, 1) + XCTAssertEqual(iosCommands.first?.name, "Test1") + XCTAssertEqual(androidCommands.count, 1) + XCTAssertEqual(androidCommands.first?.name, "Test2") + } + + func testGetCustomCommandsWithInvalidData() { + userDefaults.set("Invalid Data", forKey: "commands") + + let commands = CustomCommandService.getCustomCommands(platform: .ios, userDefaults: userDefaults) + + XCTAssertTrue(commands.isEmpty) + } + + func testGetCustomCommand() { + let commands = [ + Command(name: "Test1", command: "cmd1", icon: "icon1", platform: .ios, needBootedDevice: false), + Command(name: "Test2", command: "cmd2", icon: "icon2", platform: .android, needBootedDevice: true) + ] + let encodedCommands = try? JSONEncoder().encode(commands) + userDefaults.set(encodedCommands, forKey: "commands") + + let iosCommand = CustomCommandService.getCustomCommand( + platform: .ios, + commandName: "Test1", + userDefaults: userDefaults + ) + let androidCommand = CustomCommandService.getCustomCommand( + platform: .android, + commandName: "Test2", + userDefaults: userDefaults + ) + let nonExistentCommand = CustomCommandService.getCustomCommand( + platform: .ios, + commandName: "NonExistent", + userDefaults: userDefaults + ) + + XCTAssertNotNil(iosCommand) + XCTAssertEqual(iosCommand?.name, "Test1") + XCTAssertNotNil(androidCommand) + XCTAssertEqual(androidCommand?.name, "Test2") + XCTAssertNil(nonExistentCommand) + } + + func testRunCustomCommandIOS() { + let device = Device(name: "TestDevice", version: "15.0", identifier: "test-id", booted: true, platform: .ios, type: .virtual) + let command = Command(name: "TestCommand", command: "$xcrun_path $uuid $device_name", icon: "icon", platform: .ios, needBootedDevice: true) + + XCTAssertNoThrow(try CustomCommandService.runCustomCommand(device, command: command)) + + let expectedCommand = "\(DeviceConstants.ProcessPaths.xcrun.rawValue) test-id TestDevice" + XCTAssertEqual(shellStub.lastExecutedCommand, expectedCommand) + } + + func testRunCustomCommandAndroid() throws { + let device = Device(name: "TestDevice", version: "11", identifier: "test-id", booted: true, platform: .android, type: .physical) + let command = Command(name: "TestCommand", command: "$adb_path $adb_id $android_home_path $device_name", icon: "icon", platform: .android, needBootedDevice: true) + + XCTAssertNoThrow(try CustomCommandService.runCustomCommand(device, command: command)) + + let expectedCommand = "mocked_adb_path test-id mocked_android_home TestDevice" + XCTAssertEqual(shellStub.lastExecutedCommand, expectedCommand) + } + + func testRunCustomCommandError() { + let device = Device(name: "TestDevice", version: "15.0", identifier: "test-id", booted: true, platform: .ios, type: .virtual) + let command = Command(name: "TestCommand", command: "invalid_command", icon: "icon", platform: .ios, needBootedDevice: true) + shellStub.mockedExecute = { _, _, _ in + throw NSError(domain: "TestError", code: 1, userInfo: nil) + } + + XCTAssertThrowsError(try CustomCommandService.runCustomCommand(device, command: command)) { error in + XCTAssertTrue(error is CustomCommandError) + if case let CustomCommandError.commandError(errorMessage) = error { + XCTAssertEqual(errorMessage, "The operation couldn’t be completed. (TestError error 1.)") + } else { + XCTFail("Unexpected error type") + } + } + } +} diff --git a/MiniSimTests/CustomCommandServiceTests.swift.plist b/MiniSimTests/CustomCommandServiceTests.swift.plist new file mode 100644 index 0000000000000000000000000000000000000000..3967e063f94f2b9de2fdbeb4d90be9963443c793 GIT binary patch literal 42 dcmYc)$jK}&F)+Bm!2kw~j1ZauMnky_oB)p~1JeKi literal 0 HcmV?d00001 diff --git a/MiniSimTests/DeviceParserTests.swift b/MiniSimTests/DeviceParserTests.swift index 73b5625..1fc5010 100644 --- a/MiniSimTests/DeviceParserTests.swift +++ b/MiniSimTests/DeviceParserTests.swift @@ -6,6 +6,10 @@ class DeviceParserTests: XCTestCase { class ADB: ADBProtocol { static var shell: ShellProtocol = Shell() + static func getAndroidHome() throws -> String { + "" + } + static func getAdbId(for deviceName: String) throws -> String { if deviceName == "Nexus_5X_API_28" { throw NSError(domain: "ADBError", code: 1, userInfo: nil) @@ -282,6 +286,10 @@ class DeviceParserTests: XCTestCase { func testAndroidEmulatorParserWithADBFailure() { class FailingADB: ADBProtocol { + static func getAndroidHome() throws -> String { + "" + } + static var shell: ShellProtocol = Shell() static func getAdbId(for deviceName: String) throws -> String { diff --git a/MiniSimTests/Mocks/ShellStub.swift b/MiniSimTests/Mocks/ShellStub.swift index 4e5fd5f..130e51a 100644 --- a/MiniSimTests/Mocks/ShellStub.swift +++ b/MiniSimTests/Mocks/ShellStub.swift @@ -7,7 +7,7 @@ class ShellStub: ShellProtocol { private var _lastExecutedCommand: String = "" private var _lastPassedArguments: [String] = [] private var _lastPassedPath: String = "" - private var _mockedExecute: ((String, [String], String) -> String)? + private var _mockedExecute: ((String, [String], String) throws -> String)? var lastExecutedCommand: String { queue.sync { _lastExecutedCommand } @@ -21,7 +21,7 @@ class ShellStub: ShellProtocol { queue.sync { _lastPassedPath } } - var mockedExecute: ((String, [String], String) -> String)? { + var mockedExecute: ((String, [String], String) throws -> String)? { get { queue.sync { _mockedExecute } } set { queue.async(flags: .barrier) { self._mockedExecute = newValue } } } @@ -34,7 +34,7 @@ class ShellStub: ShellProtocol { } if let mockedExecute = queue.sync(execute: { _mockedExecute }) { - return mockedExecute(command, arguments, atPath) + return try mockedExecute(command, arguments, atPath) } return "" }