spaceship 0.0.15 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|