device_api-ios 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -4
- data/Gemfile.lock +25 -21
- data/device_api-ios.gemspec +3 -1
- data/lib/device_api/ios.rb +8 -1
- data/lib/device_api/ios/device.rb +73 -0
- data/lib/device_api/ios/idevice.rb +2 -1
- data/lib/device_api/ios/idevicedebug.rb +36 -0
- data/lib/device_api/ios/ideviceinstaller.rb +96 -0
- data/lib/device_api/ios/idevicename.rb +25 -0
- data/lib/device_api/ios/ideviceprovision.rb +98 -0
- data/lib/device_api/ios/ipaddress.rb +51 -0
- data/lib/device_api/ios/plistutil.rb +58 -0
- data/lib/device_api/ios/signing.rb +86 -0
- data/spec/ios_device_spec.rb +29 -82
- data/spec/ios_ideviceinstaller_spec.rb +97 -0
- data/spec/ios_ipaddress_spec.rb +35 -0
- data/spec/ios_plistutil_spec.rb +25 -0
- data/spec/ios_signing_spec.rb +62 -0
- metadata +42 -5
- data/spec/ios_idevice_spec.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44a319cbde8d0c83a2c6ce73f50a9f659381363e
|
4
|
+
data.tar.gz: 5a5251780401174266d85ce74f4c547f8736633b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62bd6649f4d4beceb65bbbae499a0daec718543557380eaefb6d6717c62369af546d50806961c720ee30ed8f22d20c8587777773ca842c2c3afe5f2e8110158b
|
7
|
+
data.tar.gz: 0654d20a10cc4b7d461c3d31253f50fa2ea090d9d3af46be195e62ef6cf9c2a7fbc0f7b4e1b1b637e2fab4985f0feb2276b165a502f0855f8c1cc5566bdc5a70
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,33 +1,37 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
|
-
remote: https://gems.cloud.bbc.co.uk/
|
4
3
|
specs:
|
5
|
-
|
6
|
-
|
7
|
-
columnize (>= 0.3.1)
|
8
|
-
debugger-linecache (~> 1.2.0)
|
9
|
-
debugger-ruby_core_source (~> 1.3.5)
|
10
|
-
debugger-linecache (1.2.0)
|
11
|
-
debugger-ruby_core_source (1.3.5)
|
12
|
-
device_api (1.0.0)
|
4
|
+
coderay (1.1.0)
|
5
|
+
device_api (1.0.1)
|
13
6
|
diff-lcs (1.2.5)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
7
|
+
ios-devices (0.2.1)
|
8
|
+
method_source (0.8.2)
|
9
|
+
ox (2.2.1)
|
10
|
+
pry (0.10.2)
|
11
|
+
coderay (~> 1.1.0)
|
12
|
+
method_source (~> 0.8.1)
|
13
|
+
slop (~> 3.4)
|
14
|
+
rspec (3.3.0)
|
15
|
+
rspec-core (~> 3.3.0)
|
16
|
+
rspec-expectations (~> 3.3.0)
|
17
|
+
rspec-mocks (~> 3.3.0)
|
18
|
+
rspec-core (3.3.2)
|
19
|
+
rspec-support (~> 3.3.0)
|
20
|
+
rspec-expectations (3.3.1)
|
21
21
|
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
-
rspec-support (~> 3.
|
23
|
-
rspec-mocks (3.
|
24
|
-
|
25
|
-
|
22
|
+
rspec-support (~> 3.3.0)
|
23
|
+
rspec-mocks (3.3.2)
|
24
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
+
rspec-support (~> 3.3.0)
|
26
|
+
rspec-support (3.3.0)
|
27
|
+
slop (3.6.0)
|
26
28
|
|
27
29
|
PLATFORMS
|
28
30
|
ruby
|
29
31
|
|
30
32
|
DEPENDENCIES
|
31
|
-
debugger
|
32
33
|
device_api (>= 1.0.0)
|
34
|
+
ios-devices
|
35
|
+
ox
|
36
|
+
pry
|
33
37
|
rspec
|
data/device_api-ios.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'device_api-ios'
|
3
|
-
s.version = '1.0.
|
3
|
+
s.version = '1.0.3'
|
4
4
|
s.date = Time.now.strftime("%Y-%m-%d")
|
5
5
|
s.summary = 'IOS Device Management API'
|
6
6
|
s.description = 'iOS implementation of DeviceAPI'
|
@@ -10,5 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.homepage = 'https://github.com/bbc/device_api-ios'
|
11
11
|
s.license = 'MIT'
|
12
12
|
s.add_runtime_dependency 'device_api', '>=1.0', '<2.0'
|
13
|
+
s.add_runtime_dependency 'ios-devices', '>=0.2'
|
14
|
+
s.add_runtime_dependency 'ox', '>=2.1.0'
|
13
15
|
s.add_development_dependency 'rspec'
|
14
16
|
end
|
data/lib/device_api/ios.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'device_api/ios/device'
|
3
3
|
require 'device_api/ios/idevice'
|
4
|
+
require 'device_api/ios/ideviceinstaller'
|
5
|
+
require 'device_api/ios/signing'
|
6
|
+
require 'device_api/ios/plistutil'
|
7
|
+
require 'device_api/ios/idevicedebug'
|
8
|
+
require 'device_api/ios/ipaddress'
|
9
|
+
require 'device_api/ios/ideviceprovision'
|
10
|
+
require 'device_api/ios/idevicename'
|
4
11
|
|
5
12
|
module DeviceAPI
|
6
13
|
module IOS
|
@@ -19,7 +26,7 @@ module DeviceAPI
|
|
19
26
|
if serial.to_s.empty?
|
20
27
|
raise DeviceAPI::BadSerialString.new("Serial was '#{ serial.nil? ? 'nil' : serial }'")
|
21
28
|
end
|
22
|
-
DeviceAPI::IOS::Device.new(serial: serial, state: '
|
29
|
+
DeviceAPI::IOS::Device.new(serial: serial, state: 'device')
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'device_api/device'
|
2
2
|
require 'device_api/ios/device'
|
3
3
|
require 'device_api/ios/idevice'
|
4
|
+
require 'device_api/ios/plistutil'
|
5
|
+
require 'device_api/ios/idevicename'
|
4
6
|
require 'ios/devices'
|
5
7
|
|
6
8
|
# DeviceAPI - an interface to allow for automation of devices
|
@@ -24,6 +26,12 @@ module DeviceAPI
|
|
24
26
|
}[@state]
|
25
27
|
end
|
26
28
|
|
29
|
+
# Look up device name - i.e. Bob's iPhone
|
30
|
+
# @return (String) iOS device name
|
31
|
+
def name
|
32
|
+
IDeviceName.name(serial)
|
33
|
+
end
|
34
|
+
|
27
35
|
# Look up device model using the ios-devices gem - changing 'iPad4,7' to 'iPad mini 3'
|
28
36
|
# @return (String) human readable model and version (where applicable)
|
29
37
|
def model
|
@@ -54,6 +62,64 @@ module DeviceAPI
|
|
54
62
|
IDevice.trusted?(serial)
|
55
63
|
end
|
56
64
|
|
65
|
+
# Get the app bundle ID from the specified app
|
66
|
+
# @return [String] app bundle id
|
67
|
+
def package_name(app)
|
68
|
+
app_info = Plistutil.get_bundle_id_from_app(app)
|
69
|
+
app_info['CFBundleIdentifier']
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the app version from the specified app
|
73
|
+
# @return [String] app version
|
74
|
+
def app_version_number(app)
|
75
|
+
app_info = Plistutil.get_bundle_id_from_app(app)
|
76
|
+
app_info['CFBundleVersion']
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get the IP Address from the device
|
80
|
+
# @return [String] IP Address of current device
|
81
|
+
def ip_address
|
82
|
+
IPAddress.address(serial)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get the Wifi Mac address for the current device
|
86
|
+
# @return [String] Mac address of current device
|
87
|
+
def wifi_mac_address
|
88
|
+
get_prop('WiFiAddress')
|
89
|
+
end
|
90
|
+
|
91
|
+
# Install a specified IPA
|
92
|
+
# @param [String] ipa string containing path to the IPA to install
|
93
|
+
# @return [Boolean, Exception] true when the IPA installed successfully, otherwise an error is raised
|
94
|
+
def install(ipa)
|
95
|
+
fail StandardError, 'No IPA or app specified.', caller if ipa.empty?
|
96
|
+
|
97
|
+
res = install_ipa(ipa)
|
98
|
+
|
99
|
+
fail StandardError, res, caller unless res
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
# Uninstall a specified package
|
104
|
+
# @param [String] package_name string containing name of package to uninstall
|
105
|
+
# @return [Boolean, Exception] true when the package is uninstalled successfully, otherwise an error is raised
|
106
|
+
def uninstall(package_name)
|
107
|
+
res = uninstall_package(package_name)
|
108
|
+
|
109
|
+
fail StandardError, res, caller unless res
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return whether or not the device is a tablet or mobile
|
114
|
+
# @return [Symbol] :tablet or :mobile depending on device_class
|
115
|
+
def type
|
116
|
+
if device_class.downcase == 'ipad'
|
117
|
+
:tablet
|
118
|
+
else
|
119
|
+
:mobile
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
57
123
|
private
|
58
124
|
|
59
125
|
def get_prop(key)
|
@@ -63,6 +129,13 @@ module DeviceAPI
|
|
63
129
|
@props[key]
|
64
130
|
end
|
65
131
|
|
132
|
+
def install_ipa(ipa)
|
133
|
+
IDeviceInstaller.install_ipa(ipa: ipa, serial: serial)
|
134
|
+
end
|
135
|
+
|
136
|
+
def uninstall_package(package_name)
|
137
|
+
IDeviceInstaller.uninstall_package(package: package_name, serial: serial)
|
138
|
+
end
|
66
139
|
end
|
67
140
|
end
|
68
141
|
end
|
@@ -30,7 +30,8 @@ module DeviceAPI
|
|
30
30
|
# @return true if the device returns information to ideviceinfo, otherwise false
|
31
31
|
def self.trusted?(device_id)
|
32
32
|
result = execute("ideviceinfo -u '#{device_id}'")
|
33
|
-
|
33
|
+
|
34
|
+
return true if result.exit == 0 && !result.stdout.split("\n")[0].match('Usage')
|
34
35
|
false
|
35
36
|
end
|
36
37
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# DeviceAPI - an interface to allow for automation of devices
|
2
|
+
module DeviceAPI
|
3
|
+
# iOS component of DeviceAPI
|
4
|
+
module IOS
|
5
|
+
# Namespace for all methods encapsulating idevice calls
|
6
|
+
class IDeviceDebug < Execution
|
7
|
+
|
8
|
+
# idevicedebug doesn't return until the app you are attempting to run
|
9
|
+
# exits. By passing in a timeout value we can limit how long we wait
|
10
|
+
# before terminating the debug session
|
11
|
+
# @param [Hash] options options for debug running
|
12
|
+
# @option options [String] :serial serial of the device run
|
13
|
+
# @option options [String] :bundle_id ID of the app to run
|
14
|
+
# @option options [Integer] :timeout Number of seconds before the debug session should be killed
|
15
|
+
# @return [Hash] Returns the stdout of the debug session
|
16
|
+
def self.run(options = {})
|
17
|
+
serial = options[:serial]
|
18
|
+
bundle_id = options[:bundle_id]
|
19
|
+
timeout = options[:timeout] || 10
|
20
|
+
|
21
|
+
result = execute("doalarm () { perl -e 'alarm shift; exec @ARGV' \"$@\"; }; doalarm #{timeout} idevicedebug -u #{serial} -d run #{bundle_id}")
|
22
|
+
|
23
|
+
raise IDeviceDebugError.new(result.stderr) unless [0, 255, 142].include?(result.exit)
|
24
|
+
|
25
|
+
result.stdout.split("\r\n")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Error class for the IDeviceDebug class
|
30
|
+
class IDeviceDebugError < StandardError
|
31
|
+
def initialize(msg)
|
32
|
+
super(msg)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'device_api/execution'
|
2
|
+
|
3
|
+
# DeviceAPI - an interface to allow for automation of devices
|
4
|
+
module DeviceAPI
|
5
|
+
# iOS component of DeviceAPI
|
6
|
+
module IOS
|
7
|
+
# Namespace for all methods encapsulating idevice calls
|
8
|
+
class IDeviceInstaller < Execution
|
9
|
+
# Installs a given IPA to the specified device
|
10
|
+
# @param [Hash] options options for installing the app
|
11
|
+
# @option options [String] :ipa path to the IPA to install
|
12
|
+
# @option options [String] :serial serial of the target device
|
13
|
+
# @return [Boolean] true if successful, otherwise false
|
14
|
+
def self.install_ipa(options = {})
|
15
|
+
options[:action] = :install
|
16
|
+
change_package(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Uninstalls a specified package from a device
|
20
|
+
# @param [Hash] options options for uninstalling the app
|
21
|
+
# @option options [String] :package bundle ID of the package to be uninstalled
|
22
|
+
# @option options [String] :serial serial of the target device
|
23
|
+
# @return [Boolean] true if successful, otherwise false
|
24
|
+
def self.uninstall_package(options = {})
|
25
|
+
options[:action] = :uninstall
|
26
|
+
change_package(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.change_package(options = {})
|
30
|
+
package = options[:package]
|
31
|
+
ipa = options[:ipa]
|
32
|
+
serial = options[:serial]
|
33
|
+
action = options[:action]
|
34
|
+
|
35
|
+
command = nil
|
36
|
+
if action == :install
|
37
|
+
command = "ideviceinstaller -u '#{serial}' -i '#{ipa}'"
|
38
|
+
elsif action == :uninstall
|
39
|
+
command = "ideviceinstaller -u '#{serial}' -U '#{package}'"
|
40
|
+
end
|
41
|
+
|
42
|
+
raise IDeviceInstallerError.new('No action specified') if command.nil?
|
43
|
+
|
44
|
+
result = execute(command)
|
45
|
+
|
46
|
+
raise IDeviceInstallerError.new(result.stderr) if result.exit != 0
|
47
|
+
|
48
|
+
lines = result.stdout.split("\n").map { |line| line.gsub('-', '').strip }
|
49
|
+
|
50
|
+
return true if lines.last.match('Complete')
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Lists packages installed on the specified device
|
55
|
+
# @param [String] serial serial of the target device
|
56
|
+
# @return [Hash] hash containing installed packages
|
57
|
+
def self.list_installed_packages(serial)
|
58
|
+
result = execute("ideviceinstaller -u '#{serial}' -l")
|
59
|
+
|
60
|
+
raise IDeviceInstallerError.new(result.stderr) if result.exit != 0
|
61
|
+
|
62
|
+
lines = result.stdout.split("\n")
|
63
|
+
lines.shift
|
64
|
+
packages = {}
|
65
|
+
lines.each do |line|
|
66
|
+
if /(.*)\s+-\s+(.*)\s+(\d.*)/.match(line)
|
67
|
+
packages[Regexp.last_match[2]] = { package_name: Regexp.last_match[1], version: Regexp.last_match[3] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
packages
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check to see if a package is installed
|
74
|
+
# @param [Hash] options options for checking for installed package
|
75
|
+
# @option options [String] :package package ID to check for
|
76
|
+
# @option options [String] :serial serial of the target device
|
77
|
+
# @return [Boolean] true if the package is installed, false otherwise
|
78
|
+
def self.package_installed?(options = {})
|
79
|
+
package = options[:package]
|
80
|
+
serial = options[:serial]
|
81
|
+
|
82
|
+
installed_packages = list_installed_packages(serial)
|
83
|
+
|
84
|
+
matches = installed_packages.select { |_, values| values[:package_name] == package }
|
85
|
+
return !matches.empty?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Error class for IDeviceInstaller class
|
90
|
+
class IDeviceInstallerError < StandardError
|
91
|
+
def initialize(msg)
|
92
|
+
super(msg)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# DeviceAPI - an interface to allow for automation of devices
|
2
|
+
module DeviceAPI
|
3
|
+
# iOS component of DeviceAPI
|
4
|
+
module IOS
|
5
|
+
# Namespace for all methods encapsulating idevicename calls
|
6
|
+
class IDeviceName < Execution
|
7
|
+
|
8
|
+
# Returns the device name based on the provided UUID
|
9
|
+
# @param device_id uuid of the device
|
10
|
+
# @return device name if device is connected
|
11
|
+
def self.name(device_id)
|
12
|
+
result = execute("idevicename -u #{device_id}")
|
13
|
+
return IDeviceNameError.new(result.stderr) if result.exit != 0
|
14
|
+
result.stdout.strip
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Error class for the IDeviceName class
|
19
|
+
class IDeviceNameError < StandardError
|
20
|
+
def initialize(msg)
|
21
|
+
super(msg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# DeviceAPI - an interface to allow for automation of devices
|
2
|
+
module DeviceAPI
|
3
|
+
# iOS component of DeviceAPI
|
4
|
+
module IOS
|
5
|
+
# Namespace for all methods encapsulating ideviceprovision calls
|
6
|
+
class IDeviceProvision < Execution
|
7
|
+
# Lists all profiles on the specified device
|
8
|
+
# @param [String] serial serial of the device to check
|
9
|
+
# @return [Hash] hash of profile name and UUID
|
10
|
+
def self.list_profiles(serial)
|
11
|
+
result = execute("ideviceprovision -u #{serial} list")
|
12
|
+
|
13
|
+
raise IDeviceProvisionError.new(result.stderr) if result.exit != 0
|
14
|
+
|
15
|
+
Hash[result.stdout.split("\n").map { |a| b = a.split(' - '); [b[0], b[1]] }[1..-1]]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Checks to see if a profile is installed on the specified device
|
19
|
+
# @param [Hash] options options used for checking profiles
|
20
|
+
# @option options [String] :name name of the profile (optional when uuid provided)
|
21
|
+
# @option options [String] :uuid UUID of the profile (optional when name provided)
|
22
|
+
# @option options [String] :serial serial of the device to check
|
23
|
+
# @return [Boolean] true if the profile is installed, false otherwise
|
24
|
+
def self.has_profile?(options = {})
|
25
|
+
name = options[:name]
|
26
|
+
uuid = options[:uuid]
|
27
|
+
serial = options[:serial]
|
28
|
+
|
29
|
+
profiles = list_profiles(serial)
|
30
|
+
|
31
|
+
profiles.key?(uuid) || profiles.value?(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Removes the specified profile from the device
|
35
|
+
# @param [Hash] options options used for removing a profile
|
36
|
+
# @option options [String] :uuid UUID of the profile to be removed
|
37
|
+
# @option options [String] :serial serial of the device to remove the profile from
|
38
|
+
# @return [Boolean, IDeviceProvisionError] true if the profile is removed from the device, an error otherwise
|
39
|
+
def self.remove_profile(options = {})
|
40
|
+
uuid = options[:uuid]
|
41
|
+
serial = options[:serial]
|
42
|
+
|
43
|
+
return true unless has_profile?(serial: serial, uuid: uuid)
|
44
|
+
|
45
|
+
result = execute("ideviceprovision -u #{serial} remove #{uuid}")
|
46
|
+
|
47
|
+
raise IDeviceProvisionError.new(result.stderr) if result.exit != 0
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Installs the specified profile to the device
|
52
|
+
# @param [Hash] options options used for installing a profile
|
53
|
+
# @option options [String] :file path to the provisioning profile
|
54
|
+
# @option options [String] :serial serial of the device to install the profile to
|
55
|
+
# @return [Boolean, IDeviceProvisionError] true if the profile is installed, an error otherwise
|
56
|
+
def self.install_profile(options = {})
|
57
|
+
serial = options[:serial]
|
58
|
+
file = options[:file]
|
59
|
+
|
60
|
+
info = get_profile_info(file)
|
61
|
+
|
62
|
+
# Check to see if the profile has already been added to the device
|
63
|
+
return true if has_profile?(serial: serial, uuid: info['UUID'])
|
64
|
+
|
65
|
+
result = execute("ideviceprovision -u #{serial} install #{file}")
|
66
|
+
|
67
|
+
raise IDeviceProvisionError.new(result.stderr) if result.exit != 0
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
# Gets information about a provisioning profile
|
72
|
+
# @param [String] file path to the provisioning profile
|
73
|
+
# @return [Hash] hash containing provisioning profile information
|
74
|
+
def self.get_profile_info(file)
|
75
|
+
result = execute("ideviceprovision dump #{file}")
|
76
|
+
|
77
|
+
raise IDeviceProvisionError.new(result.stderr) if result.exit != 0
|
78
|
+
|
79
|
+
lines = result.stdout.split("\n")
|
80
|
+
|
81
|
+
info = {}
|
82
|
+
lines.each do |l|
|
83
|
+
if /(.*):\s+(.*)/.match(l)
|
84
|
+
info[Regexp.last_match[1]] = Regexp.last_match[2]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
info
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Provisioning error class
|
92
|
+
class IDeviceProvisionError < StandardError
|
93
|
+
def initialize(msg)
|
94
|
+
super(msg)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'device_api/ios/idevicedebug'
|
2
|
+
require 'device_api/ios/ideviceinstaller'
|
3
|
+
|
4
|
+
# DeviceAPI - an interface to allow for automation of devices
|
5
|
+
module DeviceAPI
|
6
|
+
# iOS component of DeviceAPI
|
7
|
+
module IOS
|
8
|
+
# Namespace for all methods encapsulating idevice calls
|
9
|
+
class IPAddress < Execution
|
10
|
+
|
11
|
+
# Package name for the IP Address app
|
12
|
+
def self.ipaddress_bundle_id
|
13
|
+
'uk.co.bbc.titan.IPAddress'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check to see if the IPAddress app is installed
|
17
|
+
# @param [String] serial serial of the target device
|
18
|
+
# @return [Boolean] returns true if the app is installed
|
19
|
+
def self.installed?(serial)
|
20
|
+
if DeviceAPI::IOS::IDeviceInstaller.package_installed?( serial: serial, package: ipaddress_bundle_id )
|
21
|
+
return true
|
22
|
+
else
|
23
|
+
raise IPAddressError.new('IP Address package not installed: Please see https://github.com/bbc/ios-test-helper')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the IP Address from the installed app
|
28
|
+
# @param [String] serial serial of the target device
|
29
|
+
# @return [String] IP Address if found
|
30
|
+
def self.address(serial)
|
31
|
+
installed?(serial)
|
32
|
+
result = IDeviceDebug.run(serial: serial, bundle_id: ipaddress_bundle_id )
|
33
|
+
|
34
|
+
ip_address = nil
|
35
|
+
result.each do |line|
|
36
|
+
if /"en0\/ipv4" = "(.*)"/.match(line)
|
37
|
+
ip_address = Regexp.last_match[1]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
ip_address
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Error class for the IPAddress class
|
45
|
+
class IPAddressError < StandardError
|
46
|
+
def initialize(msg)
|
47
|
+
super(msg)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'device_api/execution'
|
2
|
+
require 'device_api/ios/signing'
|
3
|
+
require 'ox'
|
4
|
+
|
5
|
+
# DeviceAPI - an interface to allow for automation of devices
|
6
|
+
module DeviceAPI
|
7
|
+
# iOS component of DeviceAPI
|
8
|
+
module IOS
|
9
|
+
# Namespace for all methods encapsulating plistutil calls
|
10
|
+
class Plistutil < Execution
|
11
|
+
|
12
|
+
# Check to ensure that plistutil is available
|
13
|
+
# @return [Boolean] true if plistutil is available, false otherwise
|
14
|
+
def self.plistutil_available?
|
15
|
+
result = execute('which plistutil')
|
16
|
+
result.exit == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gets properties from the IPA and returns them in a hash
|
20
|
+
# @param [String] path path to the IPA/App
|
21
|
+
# @return [Hash] list of properties from the app
|
22
|
+
def self.get_bundle_id_from_app(path)
|
23
|
+
path = Signing.unpack_ipa(path) if Signing.is_ipa?(path)
|
24
|
+
get_bundle_id_from_plist("#{path}/Info.plist")
|
25
|
+
end
|
26
|
+
|
27
|
+
# Gets properties from the IPA and returns them in a hash
|
28
|
+
# @param [String] plist path to the plist
|
29
|
+
# @return [Hash] list of properties from the app
|
30
|
+
def self.get_bundle_id_from_plist(plist)
|
31
|
+
raise PlistutilCommandError.new('plistutil not found') unless plistutil_available?
|
32
|
+
result = execute("plistutil -i #{plist}")
|
33
|
+
raise PlistutilCommandError.new(result.stderr) if result.exit != 0
|
34
|
+
info = Ox.parse(result.stdout)
|
35
|
+
nodes = info.locate('*/dict')
|
36
|
+
values = {}
|
37
|
+
last_key = nil
|
38
|
+
nodes.each do |node|
|
39
|
+
node.nodes.each do |child|
|
40
|
+
if child.value == 'key'
|
41
|
+
last_key = child.nodes.first
|
42
|
+
elsif child.value == 'string'
|
43
|
+
values[last_key] = child.nodes.first
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
values
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# plistutil error class
|
52
|
+
class PlistutilCommandError < StandardError
|
53
|
+
def initialize(msg)
|
54
|
+
super(msg)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'device_api/execution'
|
2
|
+
|
3
|
+
# DeviceAPI - an interface to allow for automation of devices
|
4
|
+
module DeviceAPI
|
5
|
+
# iOS component of DeviceAPI
|
6
|
+
module IOS
|
7
|
+
# Namespace for all methods encapsulating idevice calls
|
8
|
+
class Signing < Execution
|
9
|
+
|
10
|
+
# Check to see if the path is an IPA
|
11
|
+
def self.is_ipa?(path)
|
12
|
+
return true if (File.extname path).downcase == '.ipa'
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.unpack_ipa(path)
|
17
|
+
folder = File.dirname(path)
|
18
|
+
target = (File.basename path, (File.extname path))
|
19
|
+
|
20
|
+
# Check to see if the target has already been unzipped
|
21
|
+
return Dir["#{folder}/#{target}/Payload/*.app"].first if File.exists? ("#{folder}/#{target}/Payload/")
|
22
|
+
|
23
|
+
result = execute("unzip '#{path}' -d '#{folder}/#{target}'")
|
24
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
25
|
+
|
26
|
+
Dir["#{folder}/#{target}/Payload/*.app"].first
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.is_app_signed?(app_path)
|
30
|
+
app_path = unpack_ipa(app_path) if is_ipa?(app_path)
|
31
|
+
result = execute("codesign -d -vvvv '#{app_path}'")
|
32
|
+
|
33
|
+
if result.exit != 0
|
34
|
+
return false if /is not signed/.match(result.stderr)
|
35
|
+
raise SigningCommandError.new(result.stderr)
|
36
|
+
end
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.sign_app(options = {})
|
42
|
+
cert = options[:cert]
|
43
|
+
entitlements = options[:entitlements]
|
44
|
+
app = options[:app]
|
45
|
+
|
46
|
+
result = execute("codesign --force --sign #{cert} --entitlements #{entitlements} '#{app}'")
|
47
|
+
|
48
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.get_signing_certs
|
53
|
+
result = execute('security find-identity -p codesigning -v')
|
54
|
+
|
55
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
56
|
+
|
57
|
+
certs = []
|
58
|
+
result.stdout.split("\n").each do |line|
|
59
|
+
if /\)\s*(\S*)\s*"(.*)"/.match(line)
|
60
|
+
certs << { id: Regexp.last_match[1], name: Regexp.last_match[2] }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
certs
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get_entitlements(app_path)
|
67
|
+
app_path = unpack_ipa(app_path) if is_ipa?(app_path)
|
68
|
+
result = execute("codesign -d --entitlements - #{app_path}")
|
69
|
+
|
70
|
+
require 'pry'
|
71
|
+
binding.pry
|
72
|
+
if result.exit != 0
|
73
|
+
raise SigningCommandError.new(result.stderr)
|
74
|
+
end
|
75
|
+
return result.stdout
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Signing error class
|
80
|
+
class SigningCommandError < StandardError
|
81
|
+
def initialize(msg)
|
82
|
+
super(msg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/ios_device_spec.rb
CHANGED
@@ -1,104 +1,51 @@
|
|
1
|
-
$LOAD_PATH.unshift( './lib/' )
|
2
|
-
|
3
1
|
require 'device_api/execution'
|
4
2
|
require 'device_api/ios/idevice'
|
5
3
|
require 'device_api/ios/device'
|
6
|
-
require '
|
4
|
+
require 'device_api/ios'
|
7
5
|
|
8
6
|
include RSpec
|
9
7
|
|
10
|
-
ProcessStatusStub = Struct.new(:exitstatus)
|
11
|
-
$STATUS_ZERO = ProcessStatusStub.new(0)
|
12
|
-
|
13
8
|
describe DeviceAPI::IOS do
|
14
9
|
|
15
|
-
before(:all) do
|
16
|
-
|
17
|
-
config_file = '../../config.yml'
|
18
|
-
CONFIG = YAML.load_file(File.expand_path(config_file, __FILE__))
|
19
|
-
@bundle_id = CONFIG["bundle_id"]
|
20
|
-
@app_path = File.expand_path(CONFIG["app_path"])
|
21
|
-
@arr_devices = DeviceAPI::IOS::IDevice.get_list_of_devices
|
22
|
-
keys = @arr_devices[0].keys
|
23
|
-
@test_device_uuid = keys[0]
|
24
|
-
@app_name = CONFIG["app_name"]
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
10
|
describe ".model" do
|
29
11
|
|
30
|
-
|
31
|
-
|
12
|
+
it 'returns the model of the attached device' do
|
13
|
+
device = DeviceAPI::IOS.device('123456')
|
14
|
+
expect(device.model).to eq('Unknown iOS device')
|
32
15
|
end
|
33
16
|
|
34
|
-
it
|
35
|
-
|
36
|
-
|
17
|
+
it 'returns the correct result when a device is trusted' do
|
18
|
+
device = DeviceAPI::IOS.device('123456')
|
19
|
+
output = <<end
|
20
|
+
ActivationState: Activated
|
21
|
+
ActivationStateAcknowledged: true
|
22
|
+
end
|
23
|
+
allow(Open3).to receive(:capture3) {
|
24
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
25
|
+
}
|
26
|
+
expect(device.trusted?).to eq(true)
|
37
27
|
end
|
38
28
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
describe ".install" do
|
45
|
-
|
46
|
-
before(:each) do
|
47
|
-
|
48
|
-
result = DeviceAPI::Execution.execute("ideviceinstaller -u #{@test_device_uuid} -l")
|
49
|
-
if(result.stdout.include? @bundle_id and result.stderr=="")
|
50
|
-
DeviceAPI::Execution.execute("ideviceinstaller -u '#{@test_device_uuid}' -U #{@bundle_id}")
|
51
|
-
end
|
52
|
-
|
53
|
-
@ios_device = DeviceAPI::IOS::Device.new(:serial=>@test_device_uuid,:state => "ok",:type => "Device")
|
54
|
-
|
29
|
+
it 'returns the correct result when a device is not trusted' do
|
30
|
+
device = DeviceAPI::IOS.device('123456')
|
31
|
+
expect(device.trusted?).to eq(false)
|
55
32
|
end
|
56
33
|
|
57
|
-
it
|
58
|
-
|
34
|
+
it 'returns device state' do
|
35
|
+
device = DeviceAPI::IOS.device('123456')
|
36
|
+
expect(device.status).to eq(:ok)
|
59
37
|
end
|
60
|
-
|
61
38
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if(!app_names.include? @app_name)
|
73
|
-
DeviceAPI::Execution.execute("ideviceinstaller -u '#{@test_device_uuid}' -i #{@app_path}")
|
74
|
-
@local_bundle_id = (@ios_device.bundle_id_list)[@app_name]
|
75
|
-
else
|
76
|
-
@local_bundle_id = app_bundle_list[@app_name]
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
it "Uninstalls the app from ios device" do
|
81
|
-
res = @ios_device.uninstall(@local_bundle_id)
|
82
|
-
expect( res ).to eq :success
|
83
|
-
|
39
|
+
it 'returns the device name' do
|
40
|
+
output = <<-end
|
41
|
+
Test Device
|
42
|
+
end
|
43
|
+
allow(Open3).to receive(:capture3) {
|
44
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
45
|
+
}
|
46
|
+
device = DeviceAPI::IOS.device('123456')
|
47
|
+
expect(device.name).to eq('Test Device')
|
84
48
|
end
|
85
|
-
|
86
|
-
|
87
49
|
end
|
88
|
-
|
89
|
-
describe ".get_props" do
|
90
|
-
before(:each) do
|
91
|
-
@ios_device = DeviceAPI::IOS::Device.new(:serial=>@test_device_uuid,:state => "ok",:type => "Device")
|
92
|
-
end
|
93
|
-
|
94
|
-
it "Returns a hash of name value pair properties" do
|
95
|
-
props = @ios_device.get_props
|
96
|
-
expect( props ).to be_a Hash
|
97
|
-
expect( props['ActivationState']).to eq('Activated')
|
98
|
-
end
|
99
|
-
|
100
|
-
end
|
101
|
-
|
102
|
-
|
103
50
|
end
|
104
51
|
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'device_api/ios/ideviceinstaller'
|
2
|
+
|
3
|
+
describe DeviceAPI::IOS::IDeviceInstaller do
|
4
|
+
describe '.list_installed_packages' do
|
5
|
+
it 'returns a list of installed apps' do
|
6
|
+
output = <<end
|
7
|
+
Total: 2 apps
|
8
|
+
uk.co.bbc.titan.IPAddress - IPAddress 1
|
9
|
+
uk.co.bbc.iplayer - BBC iPlayer 4.10.0.196
|
10
|
+
end
|
11
|
+
allow(Open3).to receive(:capture3) do
|
12
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
13
|
+
end
|
14
|
+
apps = DeviceAPI::IOS::IDeviceInstaller.list_installed_packages('123456')
|
15
|
+
expect(apps.count).to eq(2)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.install_ipa' do
|
20
|
+
it 'installs an app' do
|
21
|
+
output = <<end
|
22
|
+
Installing 'uk.co.mediaat.iplayer'
|
23
|
+
- CreatingStagingDirectory (5%)
|
24
|
+
- ExtractingPackage (15%)
|
25
|
+
- InspectingPackage (20%)
|
26
|
+
- TakingInstallLock (20%)
|
27
|
+
- PreflightingApplication (30%)
|
28
|
+
- InstallingEmbeddedProfile (30%)
|
29
|
+
- VerifyingApplication (40%)
|
30
|
+
- CreatingContainer (50%)
|
31
|
+
- InstallingApplication (60%)
|
32
|
+
- PostflightingApplication (70%)
|
33
|
+
- SandboxingApplication (80%)
|
34
|
+
- GeneratingApplicationMap (90%)
|
35
|
+
- Complete
|
36
|
+
end
|
37
|
+
allow(Open3).to receive(:capture3) do
|
38
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
39
|
+
end
|
40
|
+
result = DeviceAPI::IOS::IDeviceInstaller.install_ipa(serial: '123456', ipa: 'iplayer.ipa' )
|
41
|
+
expect(result).to eq(true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '.uninstall_app' do
|
46
|
+
it 'uninstalls an app' do
|
47
|
+
output = <<end
|
48
|
+
Uninstalling 'uk.co.bbc.iplayer'
|
49
|
+
- RemovingApplication (50%)
|
50
|
+
- GeneratingApplicationMap (90%)
|
51
|
+
- Complete
|
52
|
+
end
|
53
|
+
allow(Open3).to receive(:capture3) do
|
54
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
55
|
+
end
|
56
|
+
result = DeviceAPI::IOS::IDeviceInstaller.uninstall_package(package: 'uk.co.bbc.iplayer', serial: '123456')
|
57
|
+
expect(result).to eq(true)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fails to remove an app that is not installed' do
|
61
|
+
output = <<end
|
62
|
+
Uninstalling 'uk.co.bbc.iplaye'
|
63
|
+
- RemovingApplication (50%)
|
64
|
+
- GeneratingApplicationMap (90%)
|
65
|
+
- Error occurred: APIInternalError
|
66
|
+
end
|
67
|
+
allow(Open3).to receive(:capture3) do
|
68
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
69
|
+
end
|
70
|
+
result = DeviceAPI::IOS::IDeviceInstaller.uninstall_package(package: 'uk.co.bbc.iplaye', serial: '123456')
|
71
|
+
expect(result).to eq(false)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '.package_installed?' do
|
76
|
+
before(:each) do
|
77
|
+
output = <<end
|
78
|
+
Total: 2 apps
|
79
|
+
uk.co.bbc.titan.IPAddress - IPAddress 1
|
80
|
+
uk.co.bbc.iplayer - BBC iPlayer 4.10.0.196
|
81
|
+
end
|
82
|
+
allow(Open3).to receive(:capture3) do
|
83
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'identifies when a package is installed' do
|
88
|
+
result = DeviceAPI::IOS::IDeviceInstaller.package_installed?(package: 'uk.co.bbc.iplayer', serial: '123456')
|
89
|
+
expect(result).to eq(true)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'identifies when a package is not installed' do
|
93
|
+
result = DeviceAPI::IOS::IDeviceInstaller.package_installed?(package: 'uk.co.bbc.sport', serial: '123456')
|
94
|
+
expect(result).to eq(false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'device_api/execution'
|
2
|
+
require 'device_api/ios/ipaddress'
|
3
|
+
|
4
|
+
describe DeviceAPI::IOS::IPAddress do
|
5
|
+
|
6
|
+
describe '.address' do
|
7
|
+
it 'gets the correct IP Address' do
|
8
|
+
|
9
|
+
apps = <<end
|
10
|
+
Total: 2 apps\r
|
11
|
+
uk.co.bbc.titan.IPAddress - IPAddress 1\r
|
12
|
+
uk.co.bbc.iplayer - BBC iPlayer 4.10.0.196\r
|
13
|
+
end
|
14
|
+
|
15
|
+
output = <<end
|
16
|
+
2015-07-06 09:27:33.670 IPAddress[801:299377] addresses: {
|
17
|
+
"awdl0/ipv6" = "fe80::dce9:ge0f:fee7:aaad";
|
18
|
+
"en0/ipv4" = "10.10.1.80";
|
19
|
+
"en0/ipv6" = "fe80::147c:1496:fce9:ed2";
|
20
|
+
"lo0/ipv4" = "127.0.0.1";
|
21
|
+
"lo0/ipv6" = "fe80::1";
|
22
|
+
"pdp_ip0/ipv4" = "10.7.195.3";
|
23
|
+
}
|
24
|
+
2015-07-06 09:27:33.672 IPAddress[801:299377] 10.10.1.80
|
25
|
+
end
|
26
|
+
allow(Open3).to receive(:capture3).and_return(
|
27
|
+
[apps, '', (Struct.new(:exitstatus)).new(0)],
|
28
|
+
[output, '', (Struct.new(:exitstatus)).new(0)]
|
29
|
+
)
|
30
|
+
|
31
|
+
ip_address = DeviceAPI::IOS::IPAddress.address('123456')
|
32
|
+
expect(ip_address).to eq('10.10.1.80')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'device_api/ios/plistutil'
|
2
|
+
|
3
|
+
describe DeviceAPI::IOS::Plistutil do
|
4
|
+
describe ".get_app_bundle_id" do
|
5
|
+
it "returns the correct app bundle" do
|
6
|
+
|
7
|
+
xml = <<end
|
8
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
9
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
10
|
+
<plist version="1.0">
|
11
|
+
<dict>
|
12
|
+
<key>CFBundleIdentifier</key>
|
13
|
+
<string>com.example.apple-samplecode.UICatalog</string>
|
14
|
+
</dict>
|
15
|
+
</plist>
|
16
|
+
end
|
17
|
+
|
18
|
+
allow(Open3).to receive(:capture3) {
|
19
|
+
[xml, '', (Struct.new(:exitstatus)).new(0)]
|
20
|
+
}
|
21
|
+
bundle = DeviceAPI::IOS::Plistutil.get_bundle_id_from_plist('Info.plist')
|
22
|
+
expect(bundle['CFBundleIdentifier']).to eq('com.example.apple-samplecode.UICatalog')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
$LOAD_PATH.unshift( './lib/' )
|
2
|
+
|
3
|
+
require 'device_api/execution'
|
4
|
+
require 'device_api/ios/signing'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
include RSpec
|
8
|
+
|
9
|
+
describe DeviceAPI::IOS::Signing do
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".is_ipa?" do
|
16
|
+
it "correctly identifies an IPA" do
|
17
|
+
expect(DeviceAPI::IOS::Signing.is_ipa?('/path/to/ipa.ipa')).to be(true)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "correctly identifies a file that isn't an IPA" do
|
21
|
+
expect(DeviceAPI::IOS::Signing.is_ipa?('/path/to/app.app')).to be(false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".get_signing_certs" do
|
26
|
+
it "returns an Array of Hashes containing iOS Signing Certificates" do
|
27
|
+
expect(DeviceAPI::IOS::Signing.get_signing_certs).to be_kind_of(Array)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns an Array of Hashes containing correct certificates" do
|
31
|
+
out = <<-eos
|
32
|
+
1) 43ED4FA24518B1F72EE4FB3E6F7476E886A8E5D0 "iPhone Developer: Test Developer (ABC1234567)"
|
33
|
+
2) 289765876A0FB55327F8F3C2A3D4FA3F1A484CFB "iPhone Developer: Test Developer (ABC1234526)"
|
34
|
+
3) 132128763516473546816751267AFA217036217B "iPhone Developer: Test Developer (ABC1235343)"
|
35
|
+
3 valid identities found
|
36
|
+
eos
|
37
|
+
allow(Open3).to receive(:capture3) {
|
38
|
+
[out, '', (Struct.new(:exitstatus)).new(0)]
|
39
|
+
}
|
40
|
+
|
41
|
+
expect(DeviceAPI::IOS::Signing.get_signing_certs.count).to eq(3)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "entitlements" do
|
46
|
+
it 'returns a list of entitlements for an app' do
|
47
|
+
out = <<-eos
|
48
|
+
<dict>
|
49
|
+
<key>application-identifier</key>
|
50
|
+
<string>uk.co.bbc.test</string>
|
51
|
+
<key>get-task-allow</key>
|
52
|
+
<false/>
|
53
|
+
</dict>
|
54
|
+
eos
|
55
|
+
allow(Open3).to receive(:capture3) {
|
56
|
+
[out, '', (Struct.new(:exitstatus)).new(0)]
|
57
|
+
}
|
58
|
+
|
59
|
+
expect(DeviceAPI::IOS::Signing.get_entitlements('test.ipa')).to eq(true)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: device_api-ios
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- BBC
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2016-02-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: device_api
|
@@ -32,6 +32,34 @@ dependencies:
|
|
32
32
|
- - "<"
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: '2.0'
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: ios-devices
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0.2'
|
42
|
+
type: :runtime
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0.2'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: ox
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.1.0
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.1.0
|
35
63
|
- !ruby/object:Gem::Dependency
|
36
64
|
name: rspec
|
37
65
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,8 +90,18 @@ files:
|
|
62
90
|
- lib/device_api/ios.rb
|
63
91
|
- lib/device_api/ios/device.rb
|
64
92
|
- lib/device_api/ios/idevice.rb
|
93
|
+
- lib/device_api/ios/idevicedebug.rb
|
94
|
+
- lib/device_api/ios/ideviceinstaller.rb
|
95
|
+
- lib/device_api/ios/idevicename.rb
|
96
|
+
- lib/device_api/ios/ideviceprovision.rb
|
97
|
+
- lib/device_api/ios/ipaddress.rb
|
98
|
+
- lib/device_api/ios/plistutil.rb
|
99
|
+
- lib/device_api/ios/signing.rb
|
65
100
|
- spec/ios_device_spec.rb
|
66
|
-
- spec/
|
101
|
+
- spec/ios_ideviceinstaller_spec.rb
|
102
|
+
- spec/ios_ipaddress_spec.rb
|
103
|
+
- spec/ios_plistutil_spec.rb
|
104
|
+
- spec/ios_signing_spec.rb
|
67
105
|
homepage: https://github.com/bbc/device_api-ios
|
68
106
|
licenses:
|
69
107
|
- MIT
|
@@ -84,9 +122,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
122
|
version: '0'
|
85
123
|
requirements: []
|
86
124
|
rubyforge_project:
|
87
|
-
rubygems_version: 2.4.
|
125
|
+
rubygems_version: 2.4.8
|
88
126
|
signing_key:
|
89
127
|
specification_version: 4
|
90
128
|
summary: IOS Device Management API
|
91
129
|
test_files: []
|
92
|
-
has_rdoc:
|
data/spec/ios_idevice_spec.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift( './lib/' )
|
2
|
-
|
3
|
-
require 'device_api/execution'
|
4
|
-
require 'device_api/ios/idevice'
|
5
|
-
require 'yaml'
|
6
|
-
|
7
|
-
include RSpec
|
8
|
-
|
9
|
-
ProcessStatusStub = Struct.new(:exitstatus)
|
10
|
-
$STATUS_ZERO = ProcessStatusStub.new(0)
|
11
|
-
|
12
|
-
describe DeviceAPI::IOS::IDevice do
|
13
|
-
|
14
|
-
before(:all) do
|
15
|
-
|
16
|
-
config_file = '../../config.yml'
|
17
|
-
CONFIG = YAML.load_file(File.expand_path(config_file, __FILE__))
|
18
|
-
@bundle_id = CONFIG["bundle_id"]
|
19
|
-
@app_path = File.expand_path(CONFIG["app_path"])
|
20
|
-
@arr_devices = DeviceAPI::IOS::IDevice.get_list_of_devices
|
21
|
-
keys = @arr_devices[0].keys
|
22
|
-
@test_device_uuid = keys[0]
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
describe ".get_list_of_devices" do
|
27
|
-
|
28
|
-
before(:each) do
|
29
|
-
@arr_devices = DeviceAPI::IOS::IDevice.get_list_of_devices
|
30
|
-
end
|
31
|
-
|
32
|
-
it "returns an Array of connected ios devices" do
|
33
|
-
expect(@arr_devices.class).to eq(Array)
|
34
|
-
end
|
35
|
-
|
36
|
-
it "returns an Array of Hash of connected ios devices - When atleast one devices is connected" do
|
37
|
-
expect(@arr_devices[0].class).to eq(Hash)
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
describe ".get_state" do
|
43
|
-
|
44
|
-
it "returns state of a device based on the device - When atleast one device is connected" do
|
45
|
-
expect( DeviceAPI::IOS::IDevice.get_state(@test_device_uuid)).to eq("Activated")
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
describe ".install_app" do
|
52
|
-
|
53
|
-
before(:each) do
|
54
|
-
|
55
|
-
result = DeviceAPI::Execution.execute("ideviceinstaller -u #{@test_device_uuid} -l")
|
56
|
-
if(result.stdout.include? @bundle_id and result.stderr=="")
|
57
|
-
DeviceAPI::Execution.execute("ideviceinstaller -u '#{@test_device_uuid}' -U #{@bundle_id}")
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
it "returns successfully installed message once ios app is installed on a device" do
|
63
|
-
expect( DeviceAPI::IOS::IDevice.install_app(@test_device_uuid,@app_path)).to eq true
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
|
69
|
-
describe ".launch_app" do
|
70
|
-
|
71
|
-
before(:each) do
|
72
|
-
|
73
|
-
result = DeviceAPI::Execution.execute("ideviceinstaller -u #{@test_device_uuid} -l")
|
74
|
-
if(result.stdout.include? @bundle_id and result.stderr=="")
|
75
|
-
DeviceAPI::Execution.execute("ideviceinstaller -u '#{@test_device_uuid}' -U #{@bundle_id}")
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
it "Launches the app on the ios device" do
|
81
|
-
props = DeviceAPI::IOS::IDevice.launch_app(@test_device_uuid,@app_path)
|
82
|
-
expect( props ).to eq true
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
describe ".get_props" do
|
90
|
-
|
91
|
-
it "Returns a hash of name value pair properties" do
|
92
|
-
props = DeviceAPI::IOS::IDevice.get_props(@test_device_uuid)
|
93
|
-
expect( props ).to be_a Hash
|
94
|
-
expect( props['ActivationState']).to eq('Activated')
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
describe ".bundle_id_list" do
|
100
|
-
|
101
|
-
it "Returns a hash of name value pair of application name and bundle_id" do
|
102
|
-
list = DeviceAPI::IOS::IDevice.bundle_id_list(@test_device_uuid)
|
103
|
-
expect(list).to be_a Hash
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
109
|
-
|