spaceship 0.0.15 → 0.1.0
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/lib/assets/languageMapping.json +224 -0
- data/lib/spaceship.rb +20 -63
- data/lib/spaceship/base.rb +71 -14
- data/lib/spaceship/client.rb +9 -274
- data/lib/spaceship/launcher.rb +1 -1
- data/lib/spaceship/portal/app.rb +125 -0
- data/lib/spaceship/portal/certificate.rb +273 -0
- data/lib/spaceship/portal/device.rb +102 -0
- data/lib/spaceship/portal/portal.rb +6 -0
- data/lib/spaceship/portal/portal_base.rb +13 -0
- data/lib/spaceship/portal/portal_client.rb +289 -0
- data/lib/spaceship/portal/provisioning_profile.rb +369 -0
- data/lib/spaceship/portal/spaceship.rb +94 -0
- data/lib/spaceship/{ui → portal/ui}/select_team.rb +0 -0
- data/lib/spaceship/tunes/app_screenshot.rb +28 -0
- data/lib/spaceship/tunes/app_status.rb +63 -0
- data/lib/spaceship/tunes/app_submission.rb +149 -0
- data/lib/spaceship/tunes/app_version.rb +337 -0
- data/lib/spaceship/tunes/application.rb +253 -0
- data/lib/spaceship/tunes/build.rb +128 -0
- data/lib/spaceship/tunes/build_train.rb +79 -0
- data/lib/spaceship/tunes/language_converter.rb +44 -0
- data/lib/spaceship/tunes/language_item.rb +54 -0
- data/lib/spaceship/tunes/processing_build.rb +30 -0
- data/lib/spaceship/tunes/spaceship.rb +26 -0
- data/lib/spaceship/tunes/tester.rb +177 -0
- data/lib/spaceship/tunes/tunes.rb +12 -0
- data/lib/spaceship/tunes/tunes_base.rb +15 -0
- data/lib/spaceship/tunes/tunes_client.rb +360 -0
- data/lib/spaceship/version.rb +1 -1
- metadata +27 -7
- data/lib/spaceship/app.rb +0 -125
- data/lib/spaceship/certificate.rb +0 -271
- data/lib/spaceship/device.rb +0 -100
- data/lib/spaceship/provisioning_profile.rb +0 -367
@@ -0,0 +1,44 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
class LanguageConverter
|
4
|
+
class << self
|
5
|
+
# Converts the iTC format (English_CA, Brazilian Portuguese) to language short codes: (en-US, de-DE)
|
6
|
+
def from_itc_to_standard(from)
|
7
|
+
result = mapping.find { |a| a['name'] == from }
|
8
|
+
(result || {}).fetch('locale', nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Converts the language short codes: (en-US, de-DE) to the iTC format (English_CA, Brazilian Portuguese)
|
12
|
+
def from_standard_to_itc(from)
|
13
|
+
result = mapping.find { |a| a['locale'] == from || (a['alternatives'] || []).include?(from) }
|
14
|
+
(result || {}).fetch('name', nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
# Path to the gem to fetch resoures
|
19
|
+
def spaceship_gem_path
|
20
|
+
if Gem::Specification::find_all_by_name('spaceship').any?
|
21
|
+
return Gem::Specification.find_by_name('spaceship').gem_dir
|
22
|
+
else
|
23
|
+
return './'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the mapping JSON parsed
|
28
|
+
def mapping
|
29
|
+
@languages ||= JSON.parse(File.read(File.join(spaceship_gem_path, "lib", "assets", "languageMapping.json")))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class String
|
37
|
+
def to_language_code
|
38
|
+
Spaceship::Tunes::LanguageConverter.from_itc_to_standard(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_full_language
|
42
|
+
Spaceship::Tunes::LanguageConverter.from_standard_to_itc(self)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
# Represents one attribute (e.g. name) of an app in multiple languages
|
4
|
+
class LanguageItem
|
5
|
+
attr_accessor :identifier # title or description
|
6
|
+
attr_accessor :original_array # reference to original array
|
7
|
+
|
8
|
+
def initialize(identifier, ref)
|
9
|
+
self.identifier = identifier.to_s
|
10
|
+
self.original_array = ref
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
get_lang(key)[identifier]['value']
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
get_lang(key)[identifier]['value'] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_lang(lang)
|
22
|
+
result = self.original_array.find do |current|
|
23
|
+
current['language'] == lang
|
24
|
+
end
|
25
|
+
return result if result
|
26
|
+
|
27
|
+
raise "Language '#{lang}' is not activated for this app version."
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return (Array) An array containing all languages that are already available
|
31
|
+
def keys
|
32
|
+
self.original_array.collect { |l| l.fetch('language') }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return (Array) An array containing all languages that are already available
|
36
|
+
# alias for keys
|
37
|
+
def languages
|
38
|
+
keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
result = ""
|
43
|
+
self.original_array.collect do |current|
|
44
|
+
result += "#{current.fetch('language')}: #{current.fetch(identifier, {}).fetch('value')}\n"
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
inspect
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
# Represents a build which doesn't have a version number yet and is either processing or is stuck
|
4
|
+
class ProcessingBuild < Build
|
5
|
+
|
6
|
+
# @return [String] The state of this build
|
7
|
+
# @example
|
8
|
+
# ITC.apps.betaProcessingStatus.InvalidBinary
|
9
|
+
# @example
|
10
|
+
# ITC.apps.betaProcessingStatus.Created
|
11
|
+
# @example
|
12
|
+
# ITC.apps.betaProcessingStatus.Uploaded
|
13
|
+
attr_accessor :state
|
14
|
+
|
15
|
+
# @return (Integer) The number of ticks since 1970 (e.g. 1413966436000)
|
16
|
+
attr_accessor :upload_date
|
17
|
+
|
18
|
+
attr_mapping(
|
19
|
+
'state' => :state,
|
20
|
+
'uploadDate' => :upload_date
|
21
|
+
)
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def factory(attrs)
|
25
|
+
self.new(attrs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
class << self
|
4
|
+
# This client stores the default client when using the lazy syntax
|
5
|
+
# Spaceship.app instead of using the spaceship launcher
|
6
|
+
attr_accessor :client
|
7
|
+
|
8
|
+
# Authenticates with Apple's web services. This method has to be called once
|
9
|
+
# to generate a valid session. The session will automatically be used from then
|
10
|
+
# on.
|
11
|
+
#
|
12
|
+
# This method will automatically use the username from the Appfile (if available)
|
13
|
+
# and fetch the password from the Keychain (if available)
|
14
|
+
#
|
15
|
+
# @param user (String) (optional): The username (usually the email address)
|
16
|
+
# @param password (String) (optional): The password
|
17
|
+
#
|
18
|
+
# @raise InvalidUserCredentialsError: raised if authentication failed
|
19
|
+
#
|
20
|
+
# @return (Spaceship::Client) The client the login method was called for
|
21
|
+
def login(user = nil, password = nil)
|
22
|
+
@client = TunesClient.login(user, password)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
class Tester < TunesBase
|
4
|
+
|
5
|
+
# @return (String) The identifier of this tester, provided by iTunes Connect
|
6
|
+
# @example
|
7
|
+
# "60f858b4-60a8-428a-963a-f943a3d68d17"
|
8
|
+
attr_accessor :tester_id
|
9
|
+
|
10
|
+
# @return (String) The email of this tester
|
11
|
+
# @example
|
12
|
+
# "tester@spaceship.com"
|
13
|
+
attr_accessor :email
|
14
|
+
|
15
|
+
# @return (String) The first name of this tester
|
16
|
+
# @example
|
17
|
+
# "Cary"
|
18
|
+
attr_accessor :first_name
|
19
|
+
|
20
|
+
# @return (String) The last name of this tester
|
21
|
+
# @example
|
22
|
+
# "Bennett"
|
23
|
+
attr_accessor :last_name
|
24
|
+
|
25
|
+
# @return (Array) An array of registered devices for this user
|
26
|
+
# @example
|
27
|
+
# [{
|
28
|
+
# "model": "iPhone 6",
|
29
|
+
# "os": "iOS",
|
30
|
+
# "osVersion": "8.3",
|
31
|
+
# "name": null
|
32
|
+
# }]
|
33
|
+
attr_accessor :devices
|
34
|
+
|
35
|
+
attr_mapping(
|
36
|
+
'testerId' => :tester_id,
|
37
|
+
'emailAddress.value' => :email,
|
38
|
+
'firstName.value' => :first_name,
|
39
|
+
'lastName.value' => :last_name,
|
40
|
+
'devices' => :devices
|
41
|
+
)
|
42
|
+
|
43
|
+
class << self
|
44
|
+
|
45
|
+
# @return (Hash) All urls for the ITC used for web requests
|
46
|
+
def url
|
47
|
+
raise "You have to use a subclass: Internal or External"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create a new object based on a hash.
|
51
|
+
# This is used to create a new object based on the server response.
|
52
|
+
def factory(attrs)
|
53
|
+
self.new(attrs)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return (Array) Returns all beta testers available for this account
|
57
|
+
def all
|
58
|
+
client.testers(self).map { |tester| self.factory(tester) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return (Spaceship::Tunes::Tester) Returns the tester matching the parameter
|
62
|
+
# as either the Tester id or email
|
63
|
+
# @param identifier (String) (required): Value used to filter the tester
|
64
|
+
def find(identifier)
|
65
|
+
all.find do |tester|
|
66
|
+
(tester.tester_id == identifier.to_s or tester.email == identifier)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create new tester in iTunes Connect
|
71
|
+
# @param email (String) (required): The email of the new tester
|
72
|
+
# @param first_name (String) (optional): The first name of the new tester
|
73
|
+
# @param last_name (String) (optional): The last name of the new tester
|
74
|
+
# @example
|
75
|
+
# Spaceship::Tunes::Tester.external.create!(email: "tester@mathiascarignani.com", first_name: "Cary", last_name:"Bennett")
|
76
|
+
# @return (Tester): The newly created tester
|
77
|
+
def create!(email: nil, first_name: nil, last_name: nil)
|
78
|
+
data = client.create_tester!(tester: self,
|
79
|
+
email: email,
|
80
|
+
first_name: first_name,
|
81
|
+
last_name: last_name)
|
82
|
+
self.factory(data)
|
83
|
+
end
|
84
|
+
|
85
|
+
#####################################################
|
86
|
+
# @!group App
|
87
|
+
#####################################################
|
88
|
+
|
89
|
+
# @return (Array) Returns all beta testers available for this account filtered by app
|
90
|
+
# @param app_id (String) (required): The app id to filter the testers
|
91
|
+
def all_by_app(app_id)
|
92
|
+
client.testers_by_app(self, app_id).map { |tester| self.factory(tester) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return (Spaceship::Tunes::Tester) Returns the tester matching the parameter
|
96
|
+
# as either the Tester id or email
|
97
|
+
# @param app_id (String) (required): The app id to filter the testers
|
98
|
+
# @param identifier (String) (required): Value used to filter the tester
|
99
|
+
def find_by_app(app_id, identifier)
|
100
|
+
all_by_app(app_id).find do |tester|
|
101
|
+
(tester.tester_id == identifier.to_s or tester.email == identifier)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Add all testers to the app received
|
106
|
+
# @param app_id (String) (required): The app id to filter the testers
|
107
|
+
def add_all_to_app!(app_id)
|
108
|
+
# TODO: Change to not make one request for each tester
|
109
|
+
all.each do |tester|
|
110
|
+
begin
|
111
|
+
tester.add_to_app!(app_id)
|
112
|
+
rescue => ex
|
113
|
+
if ex.to_s.include?"testerEmailExistsInternal" or ex.to_s.include?"duplicate.email"
|
114
|
+
# That's a non-relevant error message by iTC
|
115
|
+
# ignore that
|
116
|
+
else
|
117
|
+
raise ex
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def setup
|
125
|
+
self.devices ||= [] # by default, an empty array instead of nil
|
126
|
+
end
|
127
|
+
|
128
|
+
#####################################################
|
129
|
+
# @!group Subclasses
|
130
|
+
#####################################################
|
131
|
+
class External < Tester
|
132
|
+
def self.url(app_id = nil)
|
133
|
+
{
|
134
|
+
index: "ra/users/pre/ext",
|
135
|
+
index_by_app: "ra/user/externalTesters/#{app_id}/",
|
136
|
+
create: "ra/users/pre/create",
|
137
|
+
delete: "ra/users/pre/ext/delete",
|
138
|
+
update_by_app: "ra/user/externalTesters/#{app_id}/"
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class Internal < Tester
|
144
|
+
def self.url(app_id = nil)
|
145
|
+
{
|
146
|
+
index: "ra/users/pre/int",
|
147
|
+
index_by_app: "ra/user/internalTesters/#{app_id}/",
|
148
|
+
create: nil,
|
149
|
+
delete: nil,
|
150
|
+
update_by_app: "ra/user/internalTesters/#{app_id}/"
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Delete current tester
|
156
|
+
def delete!
|
157
|
+
client.delete_tester!(self)
|
158
|
+
end
|
159
|
+
|
160
|
+
#####################################################
|
161
|
+
# @!group App
|
162
|
+
#####################################################
|
163
|
+
|
164
|
+
# Add current tester to list of the app testers
|
165
|
+
# @param app_id (String) (required): The id of the application to which want to modify the list
|
166
|
+
def add_to_app!(app_id)
|
167
|
+
client.add_tester_to_app!(self, app_id)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Remove current tester from list of the app testers
|
171
|
+
# @param app_id (String) (required): The id of the application to which want to modify the list
|
172
|
+
def remove_from_app!(app_id)
|
173
|
+
client.remove_tester_from_app!(self, app_id)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spaceship/tunes/tunes_base'
|
2
|
+
require 'spaceship/tunes/application'
|
3
|
+
require 'spaceship/tunes/app_version'
|
4
|
+
require 'spaceship/tunes/app_submission'
|
5
|
+
require 'spaceship/tunes/tunes_client'
|
6
|
+
require 'spaceship/tunes/language_item'
|
7
|
+
require 'spaceship/tunes/app_status'
|
8
|
+
require 'spaceship/tunes/app_screenshot'
|
9
|
+
require 'spaceship/tunes/language_converter'
|
10
|
+
require 'spaceship/tunes/build'
|
11
|
+
require 'spaceship/tunes/processing_build'
|
12
|
+
require 'spaceship/tunes/build_train'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
class TunesBase < Spaceship::Base
|
4
|
+
class << self
|
5
|
+
def client
|
6
|
+
(
|
7
|
+
@client or
|
8
|
+
Spaceship::Tunes.client or
|
9
|
+
raise "Please login using `Spaceship::Tunes.login('user', 'password')`"
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
module Spaceship
|
2
|
+
class TunesClient < Spaceship::Client
|
3
|
+
|
4
|
+
#####################################################
|
5
|
+
# @!group Init and Login
|
6
|
+
#####################################################
|
7
|
+
|
8
|
+
def self.hostname
|
9
|
+
"https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Fetches the latest login URL from iTunes Connect
|
13
|
+
def login_url
|
14
|
+
cache_path = "/tmp/spaceship_itc_login_url.txt"
|
15
|
+
begin
|
16
|
+
cached = File.read(cache_path)
|
17
|
+
rescue Errno::ENOENT
|
18
|
+
end
|
19
|
+
return cached if cached
|
20
|
+
|
21
|
+
host = "https://itunesconnect.apple.com"
|
22
|
+
begin
|
23
|
+
url = host + request(:get, self.class.hostname).body.match(/action="(\/WebObjects\/iTunesConnect.woa\/wo\/.*)"/)[1]
|
24
|
+
raise "" unless url.length > 0
|
25
|
+
|
26
|
+
File.write(cache_path, url) # TODO
|
27
|
+
return url
|
28
|
+
rescue => ex
|
29
|
+
puts ex
|
30
|
+
raise "Could not fetch the login URL from iTunes Connect, the server might be down"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_login_request(user, password)
|
35
|
+
response = request(:post, login_url, {
|
36
|
+
theAccountName: user,
|
37
|
+
theAccountPW: password
|
38
|
+
})
|
39
|
+
|
40
|
+
if response['Set-Cookie'] =~ /myacinfo=(\w+);/
|
41
|
+
# To use the session properly we'll need the following cookies:
|
42
|
+
# - myacinfo
|
43
|
+
# - woinst
|
44
|
+
# - wosid
|
45
|
+
|
46
|
+
begin
|
47
|
+
cooks = response['Set-Cookie']
|
48
|
+
|
49
|
+
to_use = [
|
50
|
+
"myacinfo=" + cooks.match(/myacinfo=(\w+)/)[1],
|
51
|
+
"woinst=" + cooks.match(/woinst=(\w+)/)[1],
|
52
|
+
"wosid=" + cooks.match(/wosid=(\w+)/)[1]
|
53
|
+
]
|
54
|
+
|
55
|
+
@cookie = to_use.join(';')
|
56
|
+
rescue => ex
|
57
|
+
# User Credentials are wrong
|
58
|
+
raise InvalidUserCredentialsError.new(response)
|
59
|
+
end
|
60
|
+
|
61
|
+
return @client
|
62
|
+
else
|
63
|
+
# User Credentials are wrong
|
64
|
+
raise InvalidUserCredentialsError.new(response)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_itc_response(data)
|
69
|
+
return unless data
|
70
|
+
return unless data.kind_of?Hash
|
71
|
+
|
72
|
+
if data.fetch('sectionErrorKeys', []).count == 0 and
|
73
|
+
data.fetch('sectionInfoKeys', []).count == 0 and
|
74
|
+
data.fetch('sectionWarningKeys', []).count == 0
|
75
|
+
|
76
|
+
logger.debug("Request was successful")
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle_response_hash(hash)
|
80
|
+
errors = []
|
81
|
+
if hash.kind_of?Hash
|
82
|
+
hash.each do |key, value|
|
83
|
+
errors = errors + handle_response_hash(value)
|
84
|
+
|
85
|
+
if key == 'errorKeys' and value.kind_of?Array and value.count > 0
|
86
|
+
errors = errors + value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
elsif hash.kind_of?Array
|
90
|
+
hash.each do |value|
|
91
|
+
errors = errors + handle_response_hash(value)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
# We don't care about simple values
|
95
|
+
end
|
96
|
+
return errors
|
97
|
+
end
|
98
|
+
|
99
|
+
errors = handle_response_hash(data)
|
100
|
+
errors = errors + data.fetch('sectionErrorKeys') if data['sectionErrorKeys']
|
101
|
+
|
102
|
+
# Sometimes there is a different kind of error in the JSON response
|
103
|
+
different_error = data.fetch('messages', {}).fetch('error', nil)
|
104
|
+
errors << different_error if different_error
|
105
|
+
|
106
|
+
raise errors.join(' ') if errors.count > 0 # they are separated by `.` by default
|
107
|
+
|
108
|
+
puts data['sectionInfoKeys'] if data['sectionInfoKeys']
|
109
|
+
puts data['sectionWarningKeys'] if data['sectionWarningKeys']
|
110
|
+
|
111
|
+
return data
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
#####################################################
|
116
|
+
# @!group Applications
|
117
|
+
#####################################################
|
118
|
+
|
119
|
+
def applications
|
120
|
+
r = request(:get, 'ra/apps/manageyourapps/summary')
|
121
|
+
parse_response(r, 'data')['summaries']
|
122
|
+
end
|
123
|
+
|
124
|
+
# Creates a new application on iTunes Connect
|
125
|
+
# @param name (String): The name of your app as it will appear on the App Store.
|
126
|
+
# This can't be longer than 255 characters.
|
127
|
+
# @param primary_language (String): If localized app information isn't available in an
|
128
|
+
# App Store territory, the information from your primary language will be used instead.
|
129
|
+
# @param version (String): The version number is shown on the App Store and should
|
130
|
+
# match the one you used in Xcode.
|
131
|
+
# @param sku (String): A unique ID for your app that is not visible on the App Store.
|
132
|
+
# @param bundle_id (String): The bundle ID must match the one you used in Xcode. It
|
133
|
+
# can't be changed after you submit your first build.
|
134
|
+
def create_application!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil)
|
135
|
+
# First, we need to fetch the data from Apple, which we then modify with the user's values
|
136
|
+
r = request(:get, 'ra/apps/create/?appType=ios')
|
137
|
+
data = parse_response(r, 'data')
|
138
|
+
|
139
|
+
# Now fill in the values we have
|
140
|
+
data['versionString']['value'] = version
|
141
|
+
data['newApp']['name']['value'] = name
|
142
|
+
data['newApp']['bundleId']['value'] = bundle_id
|
143
|
+
data['newApp']['primaryLanguage']['value'] = primary_language || 'English_CA'
|
144
|
+
data['newApp']['vendorId']['value'] = sku
|
145
|
+
data['newApp']['bundleIdSuffix']['value'] = bundle_id_suffix
|
146
|
+
|
147
|
+
# Now send back the modified hash
|
148
|
+
r = request(:post) do |req|
|
149
|
+
req.url 'ra/apps/create/?appType=ios'
|
150
|
+
req.body = data.to_json
|
151
|
+
req.headers['Content-Type'] = 'application/json'
|
152
|
+
end
|
153
|
+
|
154
|
+
data = parse_response(r, 'data')
|
155
|
+
handle_itc_response(data)
|
156
|
+
end
|
157
|
+
|
158
|
+
def create_version!(app_id, version_number)
|
159
|
+
r = request(:post) do |req|
|
160
|
+
req.url "ra/apps/version/create/#{app_id}"
|
161
|
+
req.body = { version: version_number.to_s }.to_json
|
162
|
+
req.headers['Content-Type'] = 'application/json'
|
163
|
+
end
|
164
|
+
|
165
|
+
parse_response(r, 'data')
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_resolution_center(app_id)
|
169
|
+
r = request(:get, "ra/apps/#{app_id}/resolutionCenter?v=latest")
|
170
|
+
data = parse_response(r, 'data')
|
171
|
+
end
|
172
|
+
|
173
|
+
#####################################################
|
174
|
+
# @!group AppVersions
|
175
|
+
#####################################################
|
176
|
+
|
177
|
+
def app_version(app_id, is_live)
|
178
|
+
raise "app_id is required" unless app_id
|
179
|
+
|
180
|
+
v_text = (is_live ? 'live' : nil)
|
181
|
+
|
182
|
+
r = request(:get, "ra/apps/version/#{app_id}", {v: v_text})
|
183
|
+
parse_response(r, 'data')
|
184
|
+
end
|
185
|
+
|
186
|
+
def update_app_version!(app_id, is_live, data)
|
187
|
+
raise "app_id is required" unless app_id
|
188
|
+
|
189
|
+
v_text = (is_live ? 'live' : nil)
|
190
|
+
|
191
|
+
r = request(:post) do |req|
|
192
|
+
req.url "ra/apps/version/save/#{app_id}?v=#{v_text}"
|
193
|
+
req.body = data.to_json
|
194
|
+
req.headers['Content-Type'] = 'application/json'
|
195
|
+
end
|
196
|
+
|
197
|
+
handle_itc_response(r.body['data'])
|
198
|
+
end
|
199
|
+
|
200
|
+
#####################################################
|
201
|
+
# @!group Build Trains
|
202
|
+
#####################################################
|
203
|
+
|
204
|
+
def build_trains(app_id)
|
205
|
+
raise "app_id is required" unless app_id
|
206
|
+
|
207
|
+
r = request(:get, "ra/apps/#{app_id}/trains/")
|
208
|
+
data = parse_response(r, 'data')
|
209
|
+
end
|
210
|
+
|
211
|
+
def update_build_trains!(app_id, data)
|
212
|
+
raise "app_id is required" unless app_id
|
213
|
+
|
214
|
+
r = request(:post) do |req|
|
215
|
+
req.url "ra/apps/#{app_id}/trains/"
|
216
|
+
req.body = data.to_json
|
217
|
+
req.headers['Content-Type'] = 'application/json'
|
218
|
+
end
|
219
|
+
|
220
|
+
handle_itc_response(r.body['data'])
|
221
|
+
end
|
222
|
+
|
223
|
+
#####################################################
|
224
|
+
# @!group Submit for Review
|
225
|
+
#####################################################
|
226
|
+
|
227
|
+
def send_app_submission(app_id, data, stage)
|
228
|
+
raise "app_id is required" unless app_id
|
229
|
+
|
230
|
+
r = request(:post) do |req|
|
231
|
+
req.url "ra/apps/#{app_id}/version/submit/#{stage}"
|
232
|
+
req.body = data.to_json
|
233
|
+
req.headers['Content-Type'] = 'application/json'
|
234
|
+
end
|
235
|
+
|
236
|
+
handle_itc_response(r.body['data'])
|
237
|
+
parse_response(r, 'data')
|
238
|
+
end
|
239
|
+
|
240
|
+
#####################################################
|
241
|
+
# @!group Testers
|
242
|
+
#####################################################
|
243
|
+
def testers(tester)
|
244
|
+
url = tester.url[:index]
|
245
|
+
r = request(:get, url)
|
246
|
+
parse_response(r, 'data')['testers']
|
247
|
+
end
|
248
|
+
|
249
|
+
def testers_by_app(tester, app_id)
|
250
|
+
url = tester.url(app_id)[:index_by_app]
|
251
|
+
r = request(:get, url)
|
252
|
+
parse_response(r, 'data')['users']
|
253
|
+
end
|
254
|
+
|
255
|
+
def create_tester!(tester: nil, email: nil, first_name: nil, last_name: nil)
|
256
|
+
url = tester.url[:create]
|
257
|
+
raise "Action not provided for this tester type." unless url
|
258
|
+
|
259
|
+
data = {
|
260
|
+
testers: [
|
261
|
+
{
|
262
|
+
emailAddress: {
|
263
|
+
value: email
|
264
|
+
},
|
265
|
+
firstName: {
|
266
|
+
value: first_name
|
267
|
+
},
|
268
|
+
lastName: {
|
269
|
+
value: last_name
|
270
|
+
},
|
271
|
+
testing: {
|
272
|
+
value: true
|
273
|
+
}
|
274
|
+
}
|
275
|
+
]
|
276
|
+
}
|
277
|
+
|
278
|
+
r = request(:post) do |req|
|
279
|
+
req.url url
|
280
|
+
req.body = data.to_json
|
281
|
+
req.headers['Content-Type'] = 'application/json'
|
282
|
+
end
|
283
|
+
|
284
|
+
data = parse_response(r, 'data')['testers']
|
285
|
+
handle_itc_response(data) || data[0]
|
286
|
+
end
|
287
|
+
|
288
|
+
def delete_tester!(tester)
|
289
|
+
url = tester.class.url[:delete]
|
290
|
+
raise "Action not provided for this tester type." unless url
|
291
|
+
|
292
|
+
data = [
|
293
|
+
{
|
294
|
+
emailAddress: {
|
295
|
+
value: tester.email
|
296
|
+
},
|
297
|
+
firstName: {
|
298
|
+
value: tester.first_name
|
299
|
+
},
|
300
|
+
lastName: {
|
301
|
+
value: tester.last_name
|
302
|
+
},
|
303
|
+
testing: {
|
304
|
+
value: false
|
305
|
+
},
|
306
|
+
testerId: tester.tester_id
|
307
|
+
}
|
308
|
+
]
|
309
|
+
|
310
|
+
r = request(:post) do |req|
|
311
|
+
req.url url
|
312
|
+
req.body = data.to_json
|
313
|
+
req.headers['Content-Type'] = 'application/json'
|
314
|
+
end
|
315
|
+
|
316
|
+
data = parse_response(r, 'data')['testers']
|
317
|
+
handle_itc_response(data) || data[0]
|
318
|
+
end
|
319
|
+
|
320
|
+
def add_tester_to_app!(tester, app_id)
|
321
|
+
update_tester_from_app!(tester, app_id, true)
|
322
|
+
end
|
323
|
+
|
324
|
+
def remove_tester_from_app!(tester, app_id)
|
325
|
+
update_tester_from_app!(tester, app_id, false)
|
326
|
+
end
|
327
|
+
|
328
|
+
private
|
329
|
+
def update_tester_from_app!(tester, app_id, testing)
|
330
|
+
url = tester.class.url(app_id)[:update_by_app]
|
331
|
+
data = {
|
332
|
+
users: [
|
333
|
+
{
|
334
|
+
emailAddress: {
|
335
|
+
value: tester.email
|
336
|
+
},
|
337
|
+
firstName: {
|
338
|
+
value: tester.first_name
|
339
|
+
},
|
340
|
+
lastName: {
|
341
|
+
value: tester.last_name
|
342
|
+
},
|
343
|
+
testing: {
|
344
|
+
value: testing
|
345
|
+
}
|
346
|
+
}
|
347
|
+
]
|
348
|
+
}
|
349
|
+
|
350
|
+
r = request(:post) do |req|
|
351
|
+
req.url url
|
352
|
+
req.body = data.to_json
|
353
|
+
req.headers['Content-Type'] = 'application/json'
|
354
|
+
end
|
355
|
+
|
356
|
+
data = parse_response(r, 'data')
|
357
|
+
handle_itc_response(data)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|