google-ads-common 0.6.4 → 0.7.0

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