google-ads-common 0.6.4 → 0.7.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.
@@ -25,22 +25,6 @@ require 'ads_common/savon_headers/httpi_request_proxy'
25
25
  module AdsCommon
26
26
  module SavonHeaders
27
27
  class OAuthHeaderHandler < BaseHeaderHandler
28
- # Enriches soap object with API-specific headers like namespaces, login
29
- # credentials etc. Sets the default namespace for the body to the one
30
- # specified in initializer.
31
- #
32
- # Args:
33
- # - request: a HTTPI Request for extra configuration
34
- # - soap: a Savon soap object to fill fields in
35
- # - args: request parameters to adjust for namespaces
36
- #
37
- # Returns:
38
- # - Modified soap structure
39
- #
40
- def prepare_request(request, soap, args)
41
- super(request, soap, args)
42
- generate_headers(request, soap)
43
- end
44
28
 
45
29
  private
46
30
 
@@ -55,20 +39,11 @@ module AdsCommon
55
39
  # - Hash containing a header with filled in credentials
56
40
  #
57
41
  def generate_headers(request, soap)
42
+ super(request, soap)
58
43
  credentials = @credential_handler.credentials
59
- headers = @auth_handler.headers(credentials)
60
- request_header = headers.inject({}) do |request_header, (header, value)|
61
- if header == :access_token
62
- request.url = soap.endpoint
63
- request.headers['Authorization'] =
64
- @auth_handler.generate_oauth_parameters_string(credentials,
65
- request)
66
- else
67
- request_header[prepend_namespace(header)] = value
68
- end
69
- request_header
70
- end
71
- soap.header[prepend_namespace(@element_name)] = request_header
44
+ request.url = soap.endpoint
45
+ request.headers['Authorization'] =
46
+ @auth_handler.auth_string(credentials, request)
72
47
  end
73
48
  end
74
49
  end
@@ -19,37 +19,34 @@
19
19
  #
20
20
  # Base class for all generated API services based on Savon backend.
21
21
 
22
- require 'httpi'
23
22
  require 'savon'
24
23
 
25
24
  require 'ads_common/http'
26
25
  require 'ads_common/parameters_validator'
26
+ require 'ads_common/results_extractor'
27
27
 
28
28
  module AdsCommon
29
29
  class SavonService
30
- attr_accessor :headerhandler
31
- attr_reader :api
30
+
31
+ attr_accessor :header_handler
32
+ attr_reader :config
32
33
  attr_reader :version
33
34
  attr_reader :namespace
34
35
 
35
36
  # Creates a new service.
36
- def initialize(api, endpoint, namespace, version)
37
+ def initialize(config, endpoint, namespace, version)
37
38
  if self.class() == AdsCommon::SavonService
38
39
  raise NoMethodError, 'Tried to instantiate an abstract class'
39
40
  end
40
- @api, @version, @namespace = api, version, namespace
41
- @headerhandler = []
41
+ @config, @version, @namespace = config, version, namespace
42
42
  @client = create_savon_client(endpoint, namespace)
43
43
  end
44
44
 
45
45
  private
46
46
 
47
- # Sets the logger in Savon-specific way. Also sets it for HTTPI used by it.
48
- def self.logger=(logger)
49
- Savon.configure do |config|
50
- config.log_level = :debug
51
- config.logger = logger
52
- end
47
+ # Returns currently configured Logger.
48
+ def get_logger()
49
+ return @config.read('library.logger')
53
50
  end
54
51
 
55
52
  # Returns ServiceRegistry for the current service. Has to be overridden.
@@ -68,26 +65,35 @@ module AdsCommon
68
65
  client = Savon::Client.new do |wsdl, httpi|
69
66
  wsdl.endpoint = endpoint
70
67
  wsdl.namespace = namespace
71
- AdsCommon::Http.configure_httpi(@api.config, httpi)
68
+ AdsCommon::Http.configure_httpi(@config, httpi)
69
+ end
70
+ Savon.configure do |config|
71
+ config.raise_errors = false
72
+ config.log_level = :debug
73
+ config.logger = get_logger()
72
74
  end
73
75
  return client
74
76
  end
75
77
 
76
78
  # Executes SOAP action specified as a string with given arguments.
77
79
  def execute_action(action_name, args, &block)
78
- validator = ParametersValidator.new(get_service_registry())
80
+ registry = get_service_registry()
81
+ validator = ParametersValidator.new(registry)
79
82
  args = validator.validate_args(action_name, args)
80
83
  response = execute_soap_request(
81
84
  action_name.to_sym, args, validator.extra_namespaces)
82
85
  log_headers(response.http.headers)
83
86
  handle_errors(response)
84
- return extract_result(response, action_name, &block)
87
+ extractor = ResultsExtractor.new(registry)
88
+ result = extractor.extract_result(response, action_name, &block)
89
+ run_user_block(extractor, response, result, &block) if block_given?
90
+ return result
85
91
  end
86
92
 
87
93
  # Logs response headers.
88
94
  # TODO: this needs to go on http or httpi level.
89
95
  def log_headers(headers)
90
- @api.logger.debug(headers.map {|k, v| [k, v].join(': ')}.join(', '))
96
+ get_logger().debug(headers.map {|k, v| [k, v].join(': ')}.join(', '))
91
97
  end
92
98
 
93
99
  # Executes the SOAP request with original SOAP name.
@@ -96,16 +102,15 @@ module AdsCommon
96
102
  get_service_registry.get_method_signature(action)[:original_name]
97
103
  original_action_name = action if original_action_name.nil?
98
104
  response = @client.request(original_action_name) do |soap|
99
- set_headers(soap, args, extra_namespaces)
105
+ soap.body = args
106
+ set_headers(soap, extra_namespaces)
100
107
  end
101
108
  return response
102
109
  end
103
110
 
104
111
  # Executes each handler to generate SOAP headers.
105
- def set_headers(soap, args, extra_namespaces)
106
- @headerhandler.each do |handler|
107
- handler.prepare_request(@client.http, soap, args)
108
- end
112
+ def set_headers(soap, extra_namespaces)
113
+ header_handler.prepare_request(@client.http, soap)
109
114
  soap.namespaces.merge!(extra_namespaces) unless extra_namespaces.nil?
110
115
  end
111
116
 
@@ -144,22 +149,10 @@ module AdsCommon
144
149
  end
145
150
  end
146
151
 
147
- # Extracts the finest results possible for the given result. Returns the
148
- # response itself in worst case (contents unknown).
149
- def extract_result(response, action_name, &block)
150
- method = get_service_registry.get_method_signature(action_name)
151
- action = method[:output][:name].to_sym
152
- result = response.to_hash
153
- result = result[action] if result.include?(action)
154
- result = normalize_output(result, method)
155
- run_user_block(response, result, &block) if block_given?
156
- return result
157
- end
158
-
159
152
  # Yields to user-specified block with additional information such as
160
153
  # headers.
161
- def run_user_block(response, body, &block)
162
- header = extract_header_data(response)
154
+ def run_user_block(extractor, response, body, &block)
155
+ header = extractor.extract_header_data(response)
163
156
  case block.arity
164
157
  when 1 then yield(header)
165
158
  when 2 then yield(header, body)
@@ -169,144 +162,5 @@ module AdsCommon
169
162
  end
170
163
  return nil
171
164
  end
172
-
173
- # Extracts misc data from response header.
174
- def extract_header_data(response)
175
- header_type = get_full_type_signature(:SoapResponseHeader)
176
- headers = response.header[:response_header].dup
177
- process_attributes(headers, false)
178
- result = headers.inject({}) do |result, (key, v)|
179
- normalize_output_field(headers, header_type[:fields], key)
180
- result[key] = headers[key]
181
- result
182
- end
183
- return result
184
- end
185
-
186
- # Normalizes output starting with root node "rval".
187
- def normalize_output(output_data, method_definition)
188
- fields_list = method_definition[:output][:fields]
189
- result = normalize_output_field(output_data, fields_list, :rval)
190
- return result[:rval] || result
191
- end
192
-
193
- # Normalizes one field of a given data recursively.
194
- # Args:
195
- # - output_data: XML data to normalize
196
- # - fields_list: expected list of fields from signature
197
- # - field_name: specifies field name to normalize
198
- def normalize_output_field(output_data, fields_list, field_name)
199
- return nil if output_data.nil?
200
-
201
- process_attributes(output_data, true)
202
-
203
- field_definition = get_field_by_name(fields_list, field_name)
204
- if field_definition.nil?
205
- @api.logger.warn("Can not determine type for field: %s" % field_name)
206
- return output_data
207
- end
208
-
209
- field_sym = field_name.to_sym
210
- field_data = normalize_type(output_data[field_sym], field_definition)
211
- output_data[field_sym] = field_data if field_data
212
-
213
- sub_type = get_full_type_signature(field_definition[:type])
214
- if sub_type and sub_type[:fields]
215
- # go recursive
216
- sub_type[:fields].each do |sub_type_field|
217
- field_data = output_data[field_sym]
218
- if field_data.is_a?(Array)
219
- field_data.each do |item|
220
- normalize_output_field(item, sub_type_field,
221
- sub_type_field[:name])
222
- end
223
- else
224
- normalize_output_field(field_data, sub_type_field,
225
- sub_type_field[:name])
226
- end
227
- end
228
- end
229
- return output_data
230
- end
231
-
232
- # Converts XML input string into a native format.
233
- def normalize_type(data, field)
234
- type_name = field[:type]
235
- result = case data
236
- when Array
237
- data.map {|item| normalize_item(type_name, item)}
238
- else
239
- normalize_item(type_name, data)
240
- end
241
- # If field signature allows an array, forcing array structure even for one
242
- # item.
243
- if !field[:min_occurs].nil? and
244
- (field[:max_occurs] == :unbounded ||
245
- (!field[:max_occurs].nil? and field[:max_occurs] > 1))
246
- result = arrayize(result)
247
- end
248
- return result
249
- end
250
-
251
- # Converts one leaf item to a built-in type.
252
- def normalize_item(type_name, item)
253
- return (item.nil?) ? item :
254
- case type_name
255
- when 'long', 'int' then Integer(item)
256
- when 'double', 'float' then Float(item)
257
- when 'boolean' then item.kind_of?(String) ?
258
- item.casecmp('true') == 0 : item
259
- else item
260
- end
261
- end
262
-
263
- # Finds a field in a list by its name.
264
- def get_field_by_name(fields_list, name)
265
- fields_array = arrayize(fields_list)
266
- index = fields_array.find_index {|field| field[:name].eql?(name)}
267
- return (index.nil?) ? nil : fields_array.at(index)
268
- end
269
-
270
- # Makes sure object is an array.
271
- def arrayize(object)
272
- return [] if object.nil?
273
- return object.is_a?(Array) ? object : [object]
274
- end
275
-
276
- # Returns all inherited fields of superclasses for given type.
277
- def implode_parent(data_type)
278
- result = []
279
- if data_type[:base]
280
- parent_type = get_service_registry.get_type_signature(data_type[:base])
281
- result += implode_parent(parent_type)
282
- end
283
- data_type[:fields].each do |field|
284
- # If the parent type includes a field with the same name, overwrite it.
285
- result.reject! {|parent_field| parent_field[:name].eql?(field[:name])}
286
- result << field
287
- end
288
- return result
289
- end
290
-
291
- # Returns type signature with all inherited fields.
292
- def get_full_type_signature(type_name)
293
- result = (type_name.nil?) ? nil :
294
- get_service_registry.get_type_signature(type_name)
295
- if result and result[:base]
296
- result[:fields] = implode_parent(result)
297
- end
298
- return result
299
- end
300
-
301
- # Handles attributes received from Savon.
302
- def process_attributes(data, keep_xsi_type = false)
303
- if data.kind_of?(Hash)
304
- if keep_xsi_type
305
- xsi_type = data.delete(:"@xsi:type")
306
- data[:xsi_type] = xsi_type if xsi_type
307
- end
308
- data.reject! {|key, value| key.to_s.start_with?('@')}
309
- end
310
- end
311
165
  end
312
166
  end
@@ -21,6 +21,6 @@
21
21
 
22
22
  module AdsCommon
23
23
  module ApiConfig
24
- CLIENT_LIB_VERSION = '0.6.4'
24
+ CLIENT_LIB_VERSION = '0.7.0'
25
25
  end
26
26
  end
data/test/coverage.rb ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: utf-8
3
+ #
4
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
5
+ #
6
+ # Copyright:: Copyright 2012, Google Inc. All Rights Reserved.
7
+ #
8
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17
+ # implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ # Runs test with coverage tool.
22
+
23
+ require 'simplecov'
24
+
25
+ SimpleCov.start
26
+
27
+ $:.unshift File.expand_path('../../', __FILE__)
28
+ require File.join(File.dirname(__FILE__), 'suite_unittests.rb')
29
+
30
+ # Now loading all files in the library to make sure we hit all untested files.
31
+ lib_base_path = File.expand_path('../../lib', __FILE__)
32
+ $:.unshift lib_base_path
33
+
34
+ code_files_mask = File.join(lib_base_path, '**/*.rb')
35
+ Dir.glob(code_files_mask).each {|file| require file}
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: utf-8
3
+ #
4
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
5
+ #
6
+ # Copyright:: Copyright 2012, Google Inc. All Rights Reserved.
7
+ #
8
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17
+ # implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ # Test suite for unit tests.
22
+
23
+ require 'test/unit'
24
+
25
+ $:.unshift File.expand_path('../../lib/', __FILE__)
26
+ $:.unshift File.expand_path('../../', __FILE__)
27
+
28
+ # Ads Common units tests.
29
+ test_files_mask = File.join(File.dirname(__FILE__), 'test_*.rb')
30
+ Dir.glob(test_files_mask).each {|file| require file}
@@ -28,14 +28,19 @@ require 'ads_common/auth/client_login_handler'
28
28
  module AdsCommon
29
29
  module Auth
30
30
  class ClientLoginHandler
31
+
31
32
  public :parse_token_text
32
33
  public :handle_login_error
34
+ public :validate_credentials
35
+ public :create_token_from_string
33
36
  end
34
37
  end
35
38
  end
36
39
 
40
+
37
41
  # Stub class for HTTP response.
38
42
  class ResponseStub
43
+
39
44
  attr_reader :code
40
45
  attr_reader :body
41
46
 
@@ -44,14 +49,15 @@ class ResponseStub
44
49
  end
45
50
  end
46
51
 
47
- class TestParametersValidator < Test::Unit::TestCase
48
- def setup
52
+ class TestClientLoginHandler < Test::Unit::TestCase
53
+
54
+ def setup()
49
55
  config = AdsCommon::Config.new({})
50
56
  @handler = AdsCommon::Auth::ClientLoginHandler.new(
51
- config, 'http://www.google.com', nil)
57
+ config, 'http://www.google.com', 'adwords')
52
58
  end
53
59
 
54
- def test_handle_login_error_captcha
60
+ def test_handle_login_error_captcha()
55
61
  assert_raises (AdsCommon::Errors::CaptchaRequiredError) do
56
62
  response = ResponseStub.new(403, '')
57
63
  results = {
@@ -62,15 +68,15 @@ class TestParametersValidator < Test::Unit::TestCase
62
68
  end
63
69
  end
64
70
 
65
- def test_handle_login_error_other
66
- assert_raises (AdsCommon::Errors::AuthError) do
71
+ def test_handle_login_error_other()
72
+ assert_raises(AdsCommon::Errors::AuthError) do
67
73
  response = ResponseStub.new(403, 'Body')
68
74
  results = {'Error' => 'SomeError', 'Info' => 'SomeInfo'}
69
75
  @handler.handle_login_error({}, response, results)
70
76
  end
71
77
  end
72
78
 
73
- def test_parse_token_text_simple
79
+ def test_parse_token_text_simple()
74
80
  error_str = "BadAuthentication"
75
81
  text = "Error=%s\n" % error_str
76
82
  result = @handler.parse_token_text(text)
@@ -78,7 +84,7 @@ class TestParametersValidator < Test::Unit::TestCase
78
84
  assert_equal(['Error'], result.keys)
79
85
  end
80
86
 
81
- def test_parse_token_text_captcha
87
+ def test_parse_token_text_captcha()
82
88
  captcha_token = "3u6_27iOel71j525g2tg252ge6t35g345XJtRuHYEYiTyAxsMPz2222442"
83
89
  captcha_url = "Captcha?ctoken=3u245245rgfwrg5g2fw5x3xGqQBrk_AoXXJtRuHY%3a-V"
84
90
  error_str = "CaptchaRequired"
@@ -95,4 +101,31 @@ class TestParametersValidator < Test::Unit::TestCase
95
101
  assert_equal(['CaptchaToken', 'CaptchaUrl', 'Error', 'Url'],
96
102
  result.keys.sort)
97
103
  end
104
+
105
+ def test_validate_credentials_valid()
106
+ credentials1 = {:email => 'email@example.com', :password => 'qwerty'}
107
+ credentials2 = {:auth_token => 'QazSWXEDEDCE434234'}
108
+ assert_nothing_raised do
109
+ @handler.validate_credentials(credentials1)
110
+ end
111
+ assert_nothing_raised do
112
+ @handler.validate_credentials(credentials2)
113
+ end
114
+ end
115
+
116
+ def test_validate_credentials_invalid()
117
+ credentials1 = {:email => 'email@example.com'}
118
+ credentials2 = {:password => 'qwerty'}
119
+ assert_raises(AdsCommon::Errors::AuthError) do
120
+ @handler.validate_credentials(credentials1)
121
+ end
122
+ assert_raises(AdsCommon::Errors::AuthError) do
123
+ @handler.validate_credentials(credentials2)
124
+ end
125
+ end
126
+
127
+ def test_create_token_from_string()
128
+ test_text = 'fooBar'
129
+ assert_equal(test_text, @handler.create_token_from_string(test_text))
130
+ end
98
131
  end