fastlane 2.28.0.beta.20170420010017 → 2.28.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ module Spaceship::TestFlight
2
+ class Base < Spaceship::Base
3
+ def self.client
4
+ @client ||= Client.client_with_authorization_from(Spaceship::Tunes.client)
5
+ end
6
+
7
+ def to_json
8
+ raw_data.to_json
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Spaceship::TestFlight
2
+ class BetaReviewInfo < Base
3
+ attr_accessor :contact_first_name, :contact_last_name, :contact_phone, :contact_email
4
+ attr_accessor :demo_account_name, :demo_account_password, :demo_account_required
5
+
6
+ attr_mapping({
7
+ 'contactFirstName' => :contact_first_name,
8
+ 'contactLastName' => :contact_last_name,
9
+ 'contactPhone' => :contact_phone,
10
+ 'contactEmail' => :contact_email,
11
+ 'demoAccountName' => :demo_account_name,
12
+ 'demoAccountPassword' => :demo_account_password,
13
+ 'demoAccountRequired' => :demo_account_required
14
+ })
15
+ end
16
+ end
@@ -0,0 +1,172 @@
1
+ require 'time'
2
+
3
+ module Spaceship::TestFlight
4
+ class Build < Base
5
+ # @example
6
+ # "com.sample.app"
7
+ attr_accessor :bundle_id
8
+
9
+ # @example
10
+ # "testflight.build.state.testing.active"
11
+ # @example
12
+ # "testflight.build.state.processing"
13
+ attr_accessor :internal_state
14
+
15
+ # @example
16
+ # "testflight.build.state.submit.ready"
17
+ # @example
18
+ # "testflight.build.state.processing"
19
+ attr_accessor :external_state
20
+
21
+ # Internal build ID (int)
22
+ # @example
23
+ # 19285309
24
+ attr_accessor :id
25
+
26
+ # @example
27
+ # "1.0"
28
+ attr_accessor :train_version
29
+
30
+ # @example
31
+ # "152"
32
+ attr_accessor :build_version
33
+
34
+ attr_accessor :beta_review_info
35
+
36
+ attr_accessor :export_compliance
37
+
38
+ attr_accessor :test_info
39
+
40
+ attr_accessor :install_count
41
+ attr_accessor :invite_count
42
+ attr_accessor :crash_count
43
+
44
+ attr_accessor :did_notify
45
+
46
+ attr_accessor :upload_date
47
+
48
+ attr_mapping({
49
+ 'appAdamId' => :app_id,
50
+ 'providerId' => :provider_id,
51
+ 'bundleId' => :bundle_id,
52
+ 'trainVersion' => :train_version,
53
+ 'buildVersion' => :build_version,
54
+ 'betaReviewInfo' => :beta_review_info,
55
+ 'exportCompliance' => :export_compliance,
56
+ 'internalState' => :internal_state,
57
+ 'externalState' => :external_state,
58
+ 'testInfo' => :test_info,
59
+ 'installCount' => :install_count,
60
+ 'inviteCount' => :invite_count,
61
+ 'crashCount' => :crash_count,
62
+ 'didNotify' => :did_notify,
63
+ 'uploadDate' => :upload_date,
64
+ 'id' => :id
65
+ })
66
+
67
+ BUILD_STATES = {
68
+ processing: 'testflight.build.state.processing',
69
+ active: 'testflight.build.state.testing.active',
70
+ ready: 'testflight.build.state.submit.ready',
71
+ export_compliance_missing: 'testflight.build.state.export.compliance.missing'
72
+ }
73
+
74
+ # Find a Build by `build_id`. Returns `nil` if can't find it.
75
+ #
76
+ # @return (Spaceship::TestFlight::Build)
77
+ def self.find(app_id: nil, build_id: nil)
78
+ attrs = client.get_build(app_id: app_id, build_id: build_id)
79
+ self.new(attrs) if attrs
80
+ end
81
+
82
+ def self.all(app_id: nil, platform: nil)
83
+ trains = BuildTrains.all(app_id: app_id, platform: platform)
84
+ trains.values.flatten
85
+ end
86
+
87
+ def self.builds_for_train(app_id: nil, platform: nil, train_version: nil)
88
+ builds_data = client.get_builds_for_train(app_id: app_id, platform: platform, train_version: train_version)
89
+ builds_data.map { |data| self.new(data) }
90
+ end
91
+
92
+ # Just the builds, as a flat array, that are still processing
93
+ def self.all_processing_builds(app_id: nil, platform: nil)
94
+ all(app_id: app_id, platform: platform).find_all(&:processing?)
95
+ end
96
+
97
+ def self.latest(app_id: nil, platform: nil)
98
+ all(app_id: app_id, platform: platform).sort_by(&:upload_date).last
99
+ end
100
+
101
+ # reload the raw_data resource for this build.
102
+ # This is useful when we start with a partial build response as returned by the BuildTrains,
103
+ # but then need to look up some attributes on the full build representation.
104
+ #
105
+ # Note: this will overwrite any non-saved changes to the object
106
+ #
107
+ # @return (Spaceceship::Base::DataHash) the raw_data of the build.
108
+ def reload
109
+ self.raw_data = self.class.find(app_id: app_id, build_id: id).raw_data
110
+ end
111
+
112
+ def ready_to_submit?
113
+ external_state == BUILD_STATES[:ready]
114
+ end
115
+
116
+ def active?
117
+ external_state == BUILD_STATES[:active]
118
+ end
119
+
120
+ def processing?
121
+ external_state == BUILD_STATES[:processing]
122
+ end
123
+
124
+ # Getting builds from BuildTrains only gets a partial Build object
125
+ # We are then requesting the full build from iTC when we need to access
126
+ # any of the variables below, because they are not inlcuded in the partial Build objects
127
+ #
128
+ # `super` here calls `beta_review_info` as defined by the `attr_mapping` above.
129
+ # @return (Spaceship::TestFlight::BetaReviewInfo)
130
+ def beta_review_info
131
+ super || reload
132
+ BetaReviewInfo.new(super)
133
+ end
134
+
135
+ # @return (Spaceship::TestFlight::ExportCompliance)
136
+ def export_compliance
137
+ super || reload
138
+ ExportCompliance.new(super)
139
+ end
140
+
141
+ # @return (Spaceship::TestFlight::TestInfo)
142
+ def test_info
143
+ super || reload
144
+ TestInfo.new(super)
145
+ end
146
+
147
+ # @return (Time) an parsed Time value for the upload_date
148
+ def upload_date
149
+ Time.parse(super)
150
+ end
151
+
152
+ # saves the changes to the Build object to TestFlight
153
+ def save!
154
+ client.put_build(app_id: app_id, build_id: id, build: self)
155
+ end
156
+
157
+ def update_build_information!(description: nil, feedback_email: nil, whats_new: nil)
158
+ test_info.description = description
159
+ test_info.feedback_email = feedback_email
160
+ test_info.whats_new = whats_new
161
+ save!
162
+ end
163
+
164
+ def submit_for_testflight_review!
165
+ client.post_for_testflight_review(app_id: app_id, build_id: id, build: self)
166
+ end
167
+
168
+ def add_group!(group)
169
+ client.add_group_to_build(app_id: app_id, group_id: group.id, build_id: id)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,42 @@
1
+ module Spaceship::TestFlight
2
+ class BuildTrains < Base
3
+ ##
4
+ # BuildTrains represent the collection of builds for a `train_version`
5
+ #
6
+ # Note: builds returned by BuildTrains are _partially_ complete. Properties
7
+ # such as `exportCompliance`, `testInfo` and many others are not provided.
8
+ # It is the responsibility of Build to lazy-load the necessary properties.
9
+ #
10
+ # See `Spaceship::TestFlight::Build#reload`
11
+
12
+ include Enumerable
13
+
14
+ def self.all(app_id: nil, platform: nil)
15
+ data = client.get_build_trains(app_id: app_id, platform: platform)
16
+ trains = {}
17
+ data.each do |train_version|
18
+ builds_data = client.get_builds_for_train(app_id: app_id, platform: platform, train_version: train_version)
19
+ trains[train_version] = builds_data.map { |attrs| Build.new(attrs) }
20
+ end
21
+
22
+ self.new(trains)
23
+ end
24
+
25
+ def initialize(trains = {})
26
+ @trains = trains
27
+ end
28
+
29
+ def get(key)
30
+ @trains[key]
31
+ end
32
+ alias [] get
33
+
34
+ def values
35
+ @trains.values
36
+ end
37
+
38
+ def each(&bock)
39
+ @tains.each(&block)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,145 @@
1
+ module Spaceship::TestFlight
2
+ class Client < Spaceship::Client
3
+ def self.hostname
4
+ 'https://itunesconnect.apple.com/testflight/v2/'
5
+ end
6
+
7
+ # Returns an array of all available build trains (not the builds they include)
8
+ def get_build_trains(app_id: nil, platform: nil)
9
+ assert_required_params(__method__, binding)
10
+ platform ||= "ios"
11
+ response = request(:get, "providers/#{team_id}/apps/#{app_id}/platforms/#{platform}/trains")
12
+ handle_response(response)
13
+ end
14
+
15
+ def get_builds_for_train(app_id: nil, platform: nil, train_version: nil)
16
+ assert_required_params(__method__, binding)
17
+ platform ||= "ios"
18
+
19
+ response = request(:get, "providers/#{team_id}/apps/#{app_id}/platforms/#{platform}/trains/#{train_version}/builds")
20
+ handle_response(response)
21
+ end
22
+
23
+ def post_tester(app_id: nil, tester: nil)
24
+ assert_required_params(__method__, binding)
25
+ # First we need to add the tester to the app
26
+ # It's ok if the tester already exists, we just have to do this... don't ask
27
+ # This will enable testing for the tester for a given app, as just creating the tester on an account-level
28
+ # is not enough to add the tester to a group. If this isn't done the next request would fail.
29
+ # This is a bug we reported to the iTunes Connect team, as it also happens on the iTunes Connect UI on 18. April 2017
30
+ url = "providers/#{team_id}/apps/#{app_id}/testers"
31
+ response = request(:post) do |req|
32
+ req.url url
33
+ req.body = {
34
+ "email" => tester.email,
35
+ "firstName" => tester.last_name,
36
+ "lastName" => tester.first_name
37
+ }.to_json
38
+ req.headers['Content-Type'] = 'application/json'
39
+ end
40
+ handle_response(response)
41
+ end
42
+
43
+ def put_test_to_group(app_id: nil, tester_id: nil, group_id: nil)
44
+ assert_required_params(__method__, binding)
45
+ # Then we can add the tester to the group that allows the app to test
46
+ # This is easy enough, we already have all this data. We don't need any response from the previous request
47
+ url = "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/testers/#{tester_id}"
48
+ response = request(:put) do |req|
49
+ req.url url
50
+ req.body = {
51
+ "groupId" => group_id,
52
+ "testerId" => tester_id
53
+ }.to_json
54
+ req.headers['Content-Type'] = 'application/json'
55
+ end
56
+ handle_response(response)
57
+ end
58
+
59
+ # def remove_tester_from_group!(group: nil, tester: nil, app_id: nil)
60
+ def delete_tester_from_group(group_id: nil, tester_id: nil, app_id: nil)
61
+ assert_required_params(__method__, binding)
62
+ url = "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/testers/#{tester_id}"
63
+ response = request(:delete) do |req|
64
+ req.url url
65
+ req.headers['Content-Type'] = 'application/json'
66
+ end
67
+ handle_response(response)
68
+ end
69
+
70
+ def get_build(app_id: nil, build_id: nil)
71
+ assert_required_params(__method__, binding)
72
+ response = request(:get, "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}")
73
+ handle_response(response)
74
+ end
75
+
76
+ def put_build(app_id: nil, build_id: nil, build: nil)
77
+ assert_required_params(__method__, binding)
78
+ response = request(:put) do |req|
79
+ req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}"
80
+ req.body = build.to_json
81
+ req.headers['Content-Type'] = 'application/json'
82
+ end
83
+ handle_response(response)
84
+ end
85
+
86
+ def post_for_testflight_review(app_id: nil, build_id: nil, build: nil)
87
+ assert_required_params(__method__, binding)
88
+ response = request(:post) do |req|
89
+ req.url "providers/#{team_id}/apps/#{app_id}/builds/#{build_id}/review"
90
+ req.body = build.to_json
91
+ req.headers['Content-Type'] = 'application/json'
92
+ end
93
+ handle_response(response)
94
+ end
95
+
96
+ def get_groups(app_id: nil)
97
+ assert_required_params(__method__, binding)
98
+ response = request(:get, "/testflight/v2/providers/#{team_id}/apps/#{app_id}/groups")
99
+ handle_response(response)
100
+ end
101
+
102
+ def add_group_to_build(app_id: nil, group_id: nil, build_id: nil)
103
+ body = {
104
+ 'groupId' => group_id,
105
+ 'buildId' => build_id
106
+ }
107
+ response = request(:put) do |req|
108
+ req.url "providers/#{team_id}/apps/#{app_id}/groups/#{group_id}/builds/#{build_id}"
109
+ req.body = body.to_json
110
+ req.headers['Content-Type'] = 'application/json'
111
+ end
112
+ handle_response(response)
113
+ end
114
+
115
+ def handle_response(response)
116
+ if (200..300).cover?(response.status) && response.body.empty?
117
+ return
118
+ end
119
+
120
+ unless response.body.kind_of?(Hash)
121
+ raise UnexpectedResponse, response.body
122
+ end
123
+
124
+ raise UnexpectedResponse, response.body['error'] if response.body['error']
125
+
126
+ return response.body['data'] if response.body['data']
127
+
128
+ return response.body
129
+ end
130
+
131
+ private
132
+
133
+ # used to assert all of the named parameters are supplied values
134
+ #
135
+ # @raises NameError if the values are nil
136
+ def assert_required_params(method_name, binding)
137
+ parameter_names = Hash[method(method_name).parameters].values
138
+ parameter_names.each do |name|
139
+ if binding.local_variable_get(name).nil?
140
+ raise NameError, "`#{name}' is a required parameter"
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,10 @@
1
+ module Spaceship::TestFlight
2
+ class ExportCompliance < Base
3
+ attr_accessor :uses_encryption, :encryption_updated
4
+
5
+ attr_mapping({
6
+ 'usesEncryption' => :uses_encryption,
7
+ 'encryptionUpdated' => :encryption_updated
8
+ })
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ module Spaceship::TestFlight
2
+ class Group < Base
3
+ attr_accessor :id
4
+ attr_accessor :name
5
+ attr_accessor :is_default_external_group
6
+
7
+ attr_accessor :app_id
8
+
9
+ attr_mapping({
10
+ 'id' => :id,
11
+ 'name' => :name,
12
+ 'isDefaultExternalGroup' => :is_default_external_group
13
+ })
14
+
15
+ def self.all(app_id: nil)
16
+ groups = client.get_groups(app_id: app_id)
17
+ groups.map do |g|
18
+ current_element = self.new(g)
19
+ current_element.app_id = app_id
20
+ current_element
21
+ end
22
+ end
23
+
24
+ def self.find(app_id: nil, group_name: nil)
25
+ groups = self.all(app_id: app_id)
26
+ groups.find { |g| g.name == group_name }
27
+ end
28
+
29
+ def self.default_external_group(app_id: nil)
30
+ groups = self.all(app_id: app_id)
31
+ groups.find(&:default_external_group?)
32
+ end
33
+
34
+ def self.filter_groups(app_id: nil, &block)
35
+ groups = self.all(app_id: app_id)
36
+ groups.select(&block)
37
+ end
38
+
39
+ def add_tester!(tester)
40
+ client.add_tester_to_group!(group: self, tester: tester, app_id: self.app_id)
41
+ end
42
+
43
+ def remove_tester!(tester)
44
+ client.remove_tester_from_group!(group: self, tester: tester, app_id: self.app_id)
45
+ end
46
+
47
+ def default_external_group?
48
+ is_default_external_group
49
+ end
50
+ end
51
+ end