fuelsdk 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ FuelSDK-Ruby
2
+ ============
3
+
4
+ 2013-09-11: Version 0.1.1
5
+ ```
6
+ Added ChangeLog
7
+
8
+ soap_configure, soap_perform with supporting tests
9
+
10
+ make soap_cud more rubular and easier to read and support
11
+
12
+ fixed some issues when trying to make requests after being passed a jwt
13
+ ```
@@ -27,4 +27,5 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "savon", "~> 2.0"
28
28
  spec.add_dependency "json", "~> 1.7.0"
29
29
  spec.add_dependency "jwt", "~> 0.1.6"
30
+ spec.add_dependency "activesupport", "~> 3.2.8"
30
31
  end
@@ -3,6 +3,7 @@ require "fuelsdk/version"
3
3
  require 'rubygems'
4
4
  require 'date'
5
5
  require 'jwt'
6
+ require 'active_support/inflector'
6
7
 
7
8
  module FuelSDK
8
9
  require 'fuelsdk/utils'
@@ -3,7 +3,7 @@ module FuelSDK
3
3
  # not doing accessor so user, can't update these values from response.
4
4
  # You will see in the code some of these
5
5
  # items are being updated via back doors and such.
6
- attr_reader :code, :message, :results, :request_id, :body, :raw
6
+ attr_reader :code, :message, :results, :request_id, :body, :raw, :decoded_jwt
7
7
 
8
8
  # some defaults
9
9
  def success
@@ -17,11 +17,11 @@ module FuelSDK
17
17
  end
18
18
  alias :more? :more
19
19
 
20
- def initialize raw, client
20
+ def initialize raw=nil, client=nil
21
21
  @client = client # keep connection with client in case we request more
22
22
  @results = []
23
23
  @raw = raw
24
- unpack raw
24
+ unpack raw if raw
25
25
  rescue => ex # all else fails return raw
26
26
  puts ex.message
27
27
  raw
@@ -38,16 +38,26 @@ module FuelSDK
38
38
  end
39
39
 
40
40
  class Client
41
- attr_accessor :debug, :access_token, :auth_token, :internal_token, :refresh_token,
41
+ attr_accessor :debug, :auth_token, :internal_token, :refresh_token,
42
42
  :id, :secret, :signature
43
43
 
44
44
  include FuelSDK::Soap
45
45
  include FuelSDK::Rest
46
46
 
47
+ def cache
48
+ @cache ||= {
49
+ :retrievable => {},
50
+ :editable => {}
51
+ }
52
+ end
53
+
47
54
  def jwt= encoded_jwt
48
55
  raise 'Require app signature to decode JWT' unless self.signature
49
- decoded_jwt = JWT.decode(encoded_jwt, self.signature, true)
56
+ self.decoded_jwt=JWT.decode(encoded_jwt, self.signature, true)
57
+ end
50
58
 
59
+ def decoded_jwt= decoded_jwt
60
+ @decoded_jwt = decoded_jwt
51
61
  self.auth_token = decoded_jwt['request']['user']['oauthToken']
52
62
  self.internal_token = decoded_jwt['request']['user']['internalOauthToken']
53
63
  self.refresh_token = decoded_jwt['request']['user']['refreshToken']
@@ -69,29 +79,32 @@ module FuelSDK
69
79
  self.wsdl = params["defaultwsdl"] if params["defaultwsdl"]
70
80
  end
71
81
 
72
- def refresh force=false
82
+ def request_token_data
73
83
  raise 'Require Client Id and Client Secret to refresh tokens' unless (id && secret)
84
+ Hash.new.tap do |h|
85
+ h['clientId']= id
86
+ h['clientSecret'] = secret
87
+ h['refreshToken'] = refresh_token if refresh_token
88
+ h['accessType'] = 'offline'
89
+ end
90
+ end
74
91
 
75
- if (self.access_token.nil? || force)
76
- payload = Hash.new.tap do |h|
77
- h['clientId']= id
78
- h['clientSecret'] = secret
79
- h['refreshToken'] = refresh_token if refresh_token
80
- h['accessType'] = 'offline'
81
- end
82
-
83
- options = Hash.new.tap do |h|
84
- h['data'] = payload
85
- h['content_type'] = 'application/json'
86
- h['params'] = {'legacy' => 1}
87
- end
92
+ def request_token_options data
93
+ Hash.new.tap do |h|
94
+ h['data'] = data
95
+ h['content_type'] = 'application/json'
96
+ h['params'] = {'legacy' => 1}
97
+ end
98
+ end
88
99
 
100
+ def refresh force=false
101
+ if (self.auth_token.nil? || force)
102
+ options = request_token_options(request_token_data)
89
103
  response = post("https://auth.exacttargetapis.com/v1/requestToken", options)
90
104
  raise "Unable to refresh token: #{response['message']}" unless response.has_key?('accessToken')
91
105
 
92
- self.access_token = response['accessToken']
106
+ self.auth_token = response['accessToken']
93
107
  self.internal_token = response['legacyToken']
94
- #@authTokenExpiration = Time.new + tokenResponse['expiresIn']
95
108
  self.refresh_token = response['refreshToken'] if response.has_key?("refreshToken")
96
109
  end
97
110
  end
@@ -67,7 +67,7 @@ module FuelSDK
67
67
  def rest_request action, url, options={}
68
68
  retried = false
69
69
  begin
70
- (options['params'] ||= {}).merge! 'access_token' => access_token
70
+ (options['params'] ||= {}).merge! 'access_token' => auth_token
71
71
  rsp = rest_client.send(action, url, options)
72
72
  raise 'Unauthorized' if rsp.message == 'Unauthorized'
73
73
  rescue
@@ -1,6 +1,15 @@
1
1
  require 'savon'
2
2
  module FuelSDK
3
3
 
4
+ class DescribeError < StandardError
5
+ attr_reader :response
6
+ def initialize response=nil, message=nil
7
+ response.instance_variable_set(:@message, message) # back door update
8
+ @response = response
9
+ super message
10
+ end
11
+ end
12
+
4
13
  class SoapResponse < FuelSDK::Response
5
14
 
6
15
  def continue
@@ -124,37 +133,112 @@ module FuelSDK
124
133
  soap_request :describe, message
125
134
  end
126
135
 
127
- def soap_get object_type, properties=nil, filter=nil
128
- if properties.nil? or properties.empty?
129
- rsp = soap_describe object_type
130
- if rsp.success?
131
- properties = rsp.retrievable
132
- else
133
- rsp.instance_variable_set(:@message, "Unable to get #{object_type}") # back door update
134
- return rsp
135
- end
136
+ def get_all_object_properties object_type
137
+ rsp = soap_describe object_type
138
+ raise DescribeError.new(rsp, "Unable to get #{object_type}") unless rsp.success?
139
+ rsp
140
+ end
141
+
142
+ def cache_properties action, object_type, properties
143
+ raise 'Properties should be in cache as a list' unless properties.kind_of? Array
144
+ cache[action][object_type] = properties
145
+ end
146
+
147
+ def cached_properties? action, object_type
148
+ cache[action][object_type] rescue nil
149
+ end
150
+
151
+ def retrievable_properties_cached? object_type
152
+ cached_properties? :retrievable, object_type
153
+ end
154
+
155
+ def cache_retrievable object_type, properties
156
+ cache_properties :retrievable, object_type, properties
157
+ end
158
+
159
+ def get_retrievable_properties object_type
160
+ if props=retrievable_properties_cached?(object_type)
161
+ props
162
+ else
163
+ cache_retrievable object_type, get_all_object_properties(object_type).retrievable
164
+ end
165
+ end
166
+
167
+ def editable_properties_cached? object_type
168
+ cached_properties? :editable, object_type
169
+ end
170
+
171
+ def cache_editable object_type, properties
172
+ cache_properties :editable, object_type, properties
173
+ end
174
+
175
+ def get_editable_properties object_type
176
+ if props=editable_properties_cached?(object_type)
177
+ props
178
+ else
179
+ cache_editable object_type, get_all_object_properties(object_type).editable
180
+ end
181
+ end
182
+
183
+ def normalize_properties_for_retrieve object_type, properties
184
+ if properties.nil? or properties.blank?
185
+ get_retrievable_properties object_type
136
186
  elsif properties.kind_of? Hash
137
- properties = properties.keys
187
+ properties.keys
138
188
  elsif properties.kind_of? String
139
- properties = [properties]
189
+ [properties]
190
+ elsif properties.kind_of? Symbol
191
+ [properties.to_s]
192
+ else
193
+ properties
140
194
  end
195
+ end
141
196
 
142
- message = {'ObjectType' => object_type, 'Properties' => properties}
197
+ def add_simple_filter_part filter
198
+ {
199
+ 'Filter' => filter,
200
+ :attributes! => { 'Filter' => { 'xsi:type' => 'tns:SimpleFilterPart' }}
201
+ }
202
+ end
143
203
 
144
- if filter and filter.kind_of? Hash
145
- message['Filter'] = filter
146
- message[:attributes!] = { 'Filter' => { 'xsi:type' => 'tns:SimpleFilterPart' } }
204
+ def add_complex_filter_part filter
205
+ filter[:attributes!] = {
206
+ 'LeftOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' },
207
+ 'RightOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' }
208
+ }
147
209
 
210
+ {
211
+ 'Filter' => filter,
212
+ :attributes! => { 'Filter' => { 'xsi:type' => 'tns:ComplexFilterPart' }}
213
+ }
214
+ end
215
+
216
+ def normalize_filter filter
217
+ if filter and filter.kind_of? Hash
148
218
  if filter.has_key?('LogicalOperator')
149
- message[:attributes!] = { 'Filter' => { 'xsi:type' => 'tns:ComplexFilterPart' }}
150
- message['Filter'][:attributes!] = {
151
- 'LeftOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' },
152
- 'RightOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' }}
219
+ add_complex_filter_part filter
220
+ else
221
+ add_simple_filter_part filter
153
222
  end
223
+ else
224
+ {}
154
225
  end
155
- message = {'RetrieveRequest' => message}
226
+ end
227
+
228
+ def create_object_type_message object_type, properties, filter
229
+ {'ObjectType' => object_type, 'Properties' => properties}.merge filter
230
+ end
231
+
232
+ def soap_get object_type, properties=nil, filter=nil
233
+
234
+ properties = normalize_properties_for_retrieve object_type, properties
235
+ filter = normalize_filter filter
236
+ message = create_object_type_message(object_type, properties, filter)
237
+
238
+ soap_request :retrieve, 'RetrieveRequest' => message
156
239
 
157
- soap_request :retrieve, message
240
+ rescue DescribeError => err
241
+ return err.response
158
242
  end
159
243
 
160
244
  def soap_post object_type, properties
@@ -169,43 +253,65 @@ module FuelSDK
169
253
  soap_cud :delete, object_type, properties
170
254
  end
171
255
 
172
- def soap_perform object_type, properties, action
173
- message = {
256
+ def create_action_message message_type, object_type, properties, action
257
+ properties = [properties] unless properties.kind_of? Array
258
+ {
174
259
  'Action' => action,
175
- 'Definitions' => {
176
- 'Definition' => properties,
260
+ message_type => {
261
+ message_type.singularize => properties,
177
262
  :attributes! => {
178
- 'Definition' => {'xsi:type' => ('tns:' + object_type)}
263
+ message_type.singularize => { 'xsi:type' => ('tns:' + object_type) }
179
264
  }
180
265
  }
181
266
  }
267
+ end
268
+
269
+ def soap_perform object_type, properties, action
270
+ message = create_action_message 'Definitions', object_type, properties, action
182
271
  soap_request :perform, message
183
272
  end
184
273
 
185
- private
274
+ def soap_configure object_type, properties, action
275
+ message = create_action_message 'Configurations', object_type, properties, action
276
+ soap_request :configure, message
277
+ end
186
278
 
187
- def soap_cud action, object_type, properties
188
- # get a list of attributes so we can seperate
189
- # them from standard object properties
190
- type_attrs = soap_describe(object_type).editable
191
-
192
- properties = [properties] unless properties.kind_of? Array
193
- properties.each do |p|
194
- formated_attrs = []
195
- p.each do |k, v|
196
- if type_attrs.include? k
197
- p.delete k
198
- attrs = FuelSDK.format_name_value_pairs k => v
199
- formated_attrs.concat attrs
200
- end
279
+ def create_objects_message object_type, object_properties
280
+ raise 'Object properties must be a List' unless object_properties.kind_of? Array
281
+ raise 'Object properties must be a List of Hashes' unless object_properties.first.kind_of? Hash
282
+
283
+ {
284
+ 'Objects' => object_properties,
285
+ :attributes! => {'Objects' => { 'xsi:type' => ('tns:' + object_type) }}
286
+ }
287
+ end
288
+
289
+ def normalize_properties_for_cud object_type, properties
290
+ properties = [properties] unless properties.kind_of? Array
291
+ raise 'Object properties must be a Hash' unless properties.first.kind_of? Hash
292
+
293
+ # get a list of attributes so we can seperate
294
+ # them from standard object properties
295
+ type_attrs = get_editable_properties object_type
296
+
297
+ properties.each do |p|
298
+ formated_attrs = []
299
+ p.each do |k, v|
300
+ if type_attrs.include? k
301
+ p.delete k
302
+ attrs = FuelSDK.format_name_value_pairs k => v
303
+ formated_attrs.concat attrs
201
304
  end
202
- (p['Attributes'] ||= []).concat formated_attrs unless formated_attrs.empty?
203
305
  end
306
+ (p['Attributes'] ||= []).concat formated_attrs unless formated_attrs.blank?
307
+ end
308
+ end
204
309
 
205
- message = {
206
- 'Objects' => properties,
207
- :attributes! => { 'Objects' => { 'xsi:type' => ('tns:' + object_type) } }
208
- }
310
+ private
311
+
312
+ def soap_cud action, object_type, properties
313
+ properties = normalize_properties_for_cud object_type, properties
314
+ message = create_objects_message object_type, properties
209
315
  soap_request action, message
210
316
  end
211
317
 
@@ -1,22 +1,23 @@
1
1
  module FuelSDK::Targeting
2
- attr_accessor :access_token
2
+ attr_accessor :auth_token
3
3
  attr_reader :endpoint
4
4
 
5
5
  include FuelSDK::HTTPRequest
6
6
 
7
+ def refresh
8
+ raise NotImplementedError
9
+ end
10
+
7
11
  def endpoint
8
- unless @endpoint
9
- determine_stack
10
- end
11
- @endpoint
12
+ @endpoint ||= determine_stack
12
13
  end
13
14
 
14
15
  protected
15
16
  def determine_stack
16
- options = {'params' => {'access_token' => self.access_token}}
17
+ refresh unless self.auth_token
18
+ options = {'params' => {'access_token' => self.auth_token}}
17
19
  response = get("https://www.exacttargetapis.com/platform/v1/endpoints/soap", options)
18
- @endpoint = response['url']
19
- rescue => e
20
- raise 'Unable to determine stack using: ' + e.message
20
+ raise 'Unable to determine stack' unless response.success?
21
+ response['url']
21
22
  end
22
23
  end
@@ -1,3 +1,3 @@
1
1
  module FuelSDK
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -2,6 +2,16 @@ require 'spec_helper.rb'
2
2
 
3
3
  describe FuelSDK::Client do
4
4
 
5
+ it { should respond_to :cache }
6
+
7
+ describe '#cache' do
8
+ let(:client) { FuelSDK::Client.new }
9
+
10
+ it 'its default keys are retrievable and editable' do
11
+ expect(client.cache).to eq({:retrievable => {}, :editable => {}})
12
+ end
13
+ end
14
+
5
15
  context 'initialized' do
6
16
 
7
17
  it 'with client parameters' do
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuelSDK::Response do
4
+ it_behaves_like 'Response Object'
5
+ end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper.rb'
2
- require 'objects_helper_spec.rb'
3
2
 
4
3
  describe FuelSDK::Objects::Base do
5
4
 
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuelSDK::Soap do
4
+
5
+ let(:client) { FuelSDK::Client.new }
6
+ subject {client}
7
+
8
+ it 'has inflector support' do
9
+ expect('Configurations'.singularize).to eq 'Configuration'
10
+ end
11
+
12
+ describe '#soap_configure' do
13
+ it 'makes a soap configure request with message' do
14
+ subject.should_receive(:create_action_message).with('Configurations', 'Subscriber', [], 'Do it').and_return({})
15
+ subject.should_receive(:soap_request).with(:configure, {})
16
+ subject.soap_configure 'Subscriber', [], 'Do it'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuelSDK::Soap do
4
+
5
+ let(:client) { FuelSDK::Client.new }
6
+
7
+ subject { client }
8
+
9
+ describe '#editable_properties_cached?' do
10
+ it 'returns a list of editable properties for the object' do
11
+ subject.should_receive(:cached_properties?)
12
+ .with(:editable, 'item')
13
+ .and_return(['prop'])
14
+
15
+ expect(subject.editable_properties_cached? 'item').to eq(['prop'])
16
+ end
17
+
18
+ it 'returns nil if not cached' do
19
+ expect(subject.editable_properties_cached? 'missing').to be_nil
20
+ end
21
+ end
22
+
23
+ describe '#get_editable_properties' do
24
+ it 'returns cached properties' do
25
+ subject.should_receive(:editable_properties_cached?)
26
+ .with('object')
27
+ .and_return(['prop'])
28
+
29
+ subject.should_not_receive(:get_all_object_properties)
30
+ subject.should_not_receive(:cache_editable)
31
+
32
+ expect(subject.get_editable_properties('object')).to eq ['prop']
33
+ end
34
+
35
+ it 'requests and caches properties when not in cache' do
36
+ subject.should_receive(:editable_properties_cached?)
37
+ .with('object')
38
+ .and_return(nil)
39
+
40
+ response = double(FuelSDK::DescribeResponse)
41
+ response.stub(:editable).and_return(['prop'])
42
+ subject.should_receive(:get_all_object_properties)
43
+ .and_return(response)
44
+
45
+ subject.should_receive(:cache_editable)
46
+ .with('object', ['prop'])
47
+ .and_return(['prop'])
48
+
49
+ expect(subject.get_editable_properties('object')).to eq ['prop']
50
+ end
51
+ end
52
+
53
+ describe '#cache_editable' do
54
+ it 'caches object properties to :editable' do
55
+ subject.cache_editable('Subscriber', ['Email'])
56
+ expect(subject.cache[:editable]).to eq 'Subscriber' => ['Email']
57
+ end
58
+ end
59
+
60
+ describe '#normalize_properties_for_cud' do
61
+
62
+ it 'creates soap objects properties hash putting ' \
63
+ 'custom attributes into name value pairs' do
64
+
65
+ subject.should_receive(:get_editable_properties)
66
+ .with('Subscriber')
67
+ .and_return(['FirstName'])
68
+
69
+ expect(subject.normalize_properties_for_cud(
70
+ 'Subscriber',
71
+ [{'Email' => 'dev@exacttarget.com', 'FirstName' => 'Devy'}]
72
+ )).to eq(
73
+ [{
74
+ 'Email' => 'dev@exacttarget.com',
75
+ 'Attributes' => [{'Name' => 'FirstName', 'Value' => 'Devy'}]
76
+ }]
77
+ )
78
+ end
79
+
80
+ it 'converts properties into an array' do
81
+ subject.should_receive(:get_editable_properties)
82
+ .with('Subscriber')
83
+ .and_return(['FirstName'])
84
+
85
+ expect(subject.normalize_properties_for_cud(
86
+ 'Subscriber',
87
+ {'Email' => 'dev@exacttarget.com', 'FirstName' => 'Devy'}
88
+ )).to eq(
89
+ [{
90
+ 'Email' => 'dev@exacttarget.com',
91
+ 'Attributes' => [{'Name' => 'FirstName', 'Value' => 'Devy'}]
92
+ }]
93
+ )
94
+ end
95
+
96
+ it 'raises an exception if properties are not a hash' do
97
+ expect { subject.normalize_properties_for_cud('Subscriber', 'Email') }
98
+ .to raise_error
99
+ end
100
+ end
101
+
102
+ describe '#create_objects_message' do
103
+ it 'creates hash for soap message' do
104
+ obj_attributes = [{'Name' => 'First Name', 'Value' => 'Justin'}]
105
+ expect(subject.create_objects_message('object', obj_attributes)).to eq(
106
+ {
107
+ 'Objects' => [{'Name' => 'First Name', 'Value' => 'Justin'}],
108
+ :attributes! => {'Objects' => { 'xsi:type' => 'tns:object' }}
109
+ }
110
+ )
111
+ end
112
+
113
+ it 'raises an exception if object attributes is not an Array' do
114
+ expect{ subject.create_objects_message('object', '1') }.to raise_error
115
+ end
116
+
117
+ it 'raises an exception if object attributes are not stored in a hash' do
118
+ expect{ subject.create_objects_message('object', ['1']) }.to raise_error
119
+ end
120
+ end
121
+
122
+ describe '#soap_cud' do
123
+ it 'request with message created with normalized properties' do
124
+
125
+ subject.should_receive(:normalize_properties_for_cud)
126
+ subject.should_receive(:create_objects_message)
127
+ subject.should_receive(:soap_request)
128
+
129
+ subject.send :soap_cud, :post, 'Subscriber', [{'EmailAddress' => 'dev@exacttarget.com'}]
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuelSDK::DescribeError do
4
+
5
+ it { should respond_to(:response) }
6
+
7
+ let (:triggering) { FuelSDK::SoapResponse.new }
8
+
9
+ it 'has passed message as error' do
10
+ error = FuelSDK::DescribeError.new(triggering, 'i am an error message')
11
+ expect(error.message).to eq 'i am an error message'
12
+ end
13
+
14
+ it 'triggering response is available' do
15
+ error = FuelSDK::DescribeError.new(triggering, 'i am an error message')
16
+ expect(error.response).to eq triggering
17
+ end
18
+
19
+ it 'sets message on response' do
20
+ expect(triggering.message).to be_nil
21
+ error = FuelSDK::DescribeError.new(triggering, 'i am an error message')
22
+ expect(triggering.message).to eq 'i am an error message'
23
+ end
24
+
25
+ end
@@ -0,0 +1,257 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuelSDK::Soap do
4
+
5
+ let(:client) { FuelSDK::Client.new }
6
+ subject { client }
7
+
8
+ describe '#get_all_object_properties' do
9
+
10
+ it 'returns properties for object_type' do
11
+ response = mock(FuelSDK::DescribeResponse)
12
+ response.should_receive(:success?).and_return(true)
13
+
14
+ subject.should_receive(:soap_describe)
15
+ .with('some object')
16
+ .and_return(response)
17
+
18
+ expect(subject.get_all_object_properties('some object'))
19
+ .to eq response
20
+ end
21
+
22
+ it 'raises an DescribeError when describe is unsuccessful' do
23
+ response = mock(FuelSDK::DescribeResponse)
24
+ response.should_receive(:success?).and_return(false)
25
+ response.stub(:status).and_return('ERROR')
26
+
27
+ subject.should_receive(:soap_describe)
28
+ .with('some object')
29
+ .and_return(response)
30
+
31
+ expect { subject.get_all_object_properties('some object') }
32
+ .to raise_error FuelSDK::DescribeError
33
+ end
34
+ end
35
+
36
+ describe '#normalize_properties_for_retrieve' do
37
+ it 'when properties are nil gets_all_object_properties' do
38
+ subject.should_receive(:get_retrievable_properties)
39
+ .with('object').and_return('called all')
40
+
41
+ expect(subject.normalize_properties_for_retrieve('object', nil)).to eq 'called all'
42
+ end
43
+
44
+ describe 'when properties is a' do
45
+ subject {
46
+ client.should_not_receive(:get_retrievable_properties)
47
+ client
48
+ }
49
+
50
+ it 'Hash returns keys' do
51
+ expect(subject.normalize_properties_for_retrieve('object', {'Prop1' => 'a', 'Prop2' => 'b'}))
52
+ .to eq ['Prop1', 'Prop2']
53
+ end
54
+
55
+ it 'String returns Array' do
56
+ expect(subject.normalize_properties_for_retrieve('object', 'Prop1'))
57
+ .to eq ['Prop1']
58
+ end
59
+
60
+ it 'Symbol returns Array' do
61
+ expect(subject.normalize_properties_for_retrieve('object', :Prop1))
62
+ .to eq ['Prop1']
63
+ end
64
+
65
+ it 'Array returns Array' do
66
+ expect(subject.normalize_properties_for_retrieve('object', ['Prop1']))
67
+ .to eq ['Prop1']
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#normalize_filter' do
73
+ it 'returns complex filter part when filter contains LogicalOperator key' do
74
+ expect(subject.normalize_filter({'LogicalOperator' => 'AND'}))
75
+ .to eq(
76
+ {
77
+ 'Filter' => {
78
+ 'LogicalOperator' => 'AND',
79
+ :attributes! => {
80
+ 'LeftOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' },
81
+ 'RightOperand' => { 'xsi:type' => 'tns:SimpleFilterPart' }
82
+ },
83
+ },
84
+ :attributes! => { 'Filter' => { 'xsi:type' => 'tns:ComplexFilterPart' }}
85
+ }
86
+ )
87
+ end
88
+
89
+ it 'returns simple filter part by default' do
90
+ expect(subject.normalize_filter({'SimpleOperator' => 'equals'}))
91
+ .to eq(
92
+ {
93
+ 'Filter' => {
94
+ 'SimpleOperator' => 'equals',
95
+ },
96
+ :attributes! => { 'Filter' => { 'xsi:type' => 'tns:SimpleFilterPart' }}
97
+ }
98
+ )
99
+ end
100
+
101
+ it 'returns empty hash when no filter' do
102
+ expect(subject.normalize_filter(nil)).to eq({})
103
+ end
104
+
105
+ it 'returns empty hash when filter is unparsable' do
106
+ expect(subject.normalize_filter(['unparsable'])).to eq({})
107
+ end
108
+ end
109
+
110
+ describe '#cache_properties' do
111
+ it 'raise an error if properties is not an Array' do
112
+
113
+ subject.should_not_receive(:cache)
114
+ expect { subject.cache_properties :retrievable, 'Subscriber', 'EmailAddress' }
115
+ .to raise_error
116
+ end
117
+
118
+ it 'caches properties' do
119
+ subject.should_receive(:cache).and_return({:retrievable => {}})
120
+ expect(subject.cache_properties :retrievable, 'Subscriber', ['EmailAddress'])
121
+ .to eq(['EmailAddress'])
122
+ end
123
+ end
124
+
125
+ describe '#cached_properties?' do
126
+ it 'returns cached properties' do
127
+ subject.should_receive(:cache).and_return(
128
+ {
129
+ :retrievable => {
130
+ 'Subscriber' => ['EmailAddress']}
131
+ }
132
+ )
133
+
134
+ expect(subject.cached_properties?(:retrievable, 'Subscriber'))
135
+ .to eq ['EmailAddress']
136
+ end
137
+
138
+ it 'returns nil on error access cache' do
139
+ subject.should_receive(:cache).and_return(1)
140
+ expect(subject.cached_properties?(:retrievable, 'Subscriber'))
141
+ .to be_nil
142
+ end
143
+ end
144
+
145
+ describe '#retrievable_properties_cached?' do
146
+ it 'returns a list of retrievable properties for the object' do
147
+ subject.should_receive(:cached_properties?)
148
+ .with(:retrievable, 'item')
149
+ .and_return(['prop'])
150
+
151
+ expect(subject.retrievable_properties_cached? 'item').to eq(['prop'])
152
+ end
153
+
154
+ it 'returns nil if not cached' do
155
+ expect(subject.retrievable_properties_cached? 'missing').to be_nil
156
+ end
157
+ end
158
+
159
+ describe '#get_retrievable_properties' do
160
+ it 'returns cached properties' do
161
+ subject.should_receive(:retrievable_properties_cached?)
162
+ .with('object')
163
+ .and_return(['prop'])
164
+
165
+ subject.should_not_receive(:get_all_object_properties)
166
+ subject.should_not_receive(:cache_retrievable)
167
+
168
+ expect(subject.get_retrievable_properties('object')).to eq ['prop']
169
+ end
170
+
171
+ it 'requests and caches properties when not in cache' do
172
+ subject.should_receive(:retrievable_properties_cached?)
173
+ .with('object')
174
+ .and_return(nil)
175
+
176
+ response = mock(FuelSDK::DescribeResponse)
177
+ response.stub(:retrievable).and_return(['prop'])
178
+ subject.should_receive(:get_all_object_properties)
179
+ .and_return(response)
180
+
181
+ subject.should_receive(:cache_retrievable)
182
+ .with('object', ['prop'])
183
+ .and_return(['prop'])
184
+
185
+ expect(subject.get_retrievable_properties('object')).to eq ['prop']
186
+ end
187
+ end
188
+
189
+ describe '#cache_retrievable' do
190
+ it 'caches object properties to :retrievable' do
191
+ subject.cache_retrievable('Subscriber', ['Email'])
192
+ expect(subject.cache[:retrievable]).to eq 'Subscriber' => ['Email']
193
+ end
194
+ end
195
+
196
+ describe '#soap_get' do
197
+ it 'request with message created with normalized properties, filters' do
198
+
199
+ subject.should_receive(:normalize_properties_for_retrieve)
200
+ .with('end to end', nil).and_return([])
201
+
202
+ subject.should_receive(:normalize_filter)
203
+ .with(nil).and_return({})
204
+
205
+ subject.should_receive(:create_object_type_message)
206
+ .with('end to end', [], {}).and_return('message')
207
+
208
+ subject.should_receive(:soap_request)
209
+ .with(:retrieve, 'RetrieveRequest' => 'message')
210
+
211
+ subject.soap_get 'end to end'
212
+ end
213
+
214
+ it 'request an object without passing properties or a filter' do
215
+
216
+ subject.should_receive(:get_retrievable_properties)
217
+ .with('no criteria').and_return(['Props1'])
218
+
219
+ subject.should_not_receive(:add_complex_filter_part)
220
+ subject.should_not_receive(:add_simple_filter_part)
221
+
222
+ subject.should_receive(:soap_request).with(:retrieve, 'RetrieveRequest' => {
223
+ 'ObjectType' => 'no criteria',
224
+ 'Properties' => ['Props1']
225
+ }
226
+ )
227
+
228
+ subject.soap_get 'no criteria'
229
+ end
230
+
231
+ it 'request an object with limited properties' do
232
+
233
+ subject.should_not_receive(:get_retrievable_properties)
234
+ subject.should_not_receive(:add_complex_fitler_part)
235
+ subject.should_not_receive(:add_simple_fitler_part)
236
+
237
+ subject.should_receive(:soap_request).with(:retrieve, 'RetrieveRequest' => {
238
+ 'ObjectType' => 'limited',
239
+ 'Properties' => ['Props1']
240
+ }
241
+ )
242
+
243
+ subject.soap_get('limited', ['Props1'])
244
+ end
245
+
246
+ it 'request an invalid object without properties' do
247
+ subject.should_receive(:get_retrievable_properties) { raise FuelSDK::DescribeError.new(
248
+ FuelSDK::DescribeResponse.new, "Unable to get invalid"
249
+ )
250
+ }
251
+
252
+ rsp = subject.soap_get('invalid')
253
+ expect(rsp.success?).to be_false
254
+ end
255
+ end
256
+
257
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe FuelSDK::Soap do
4
+
5
+ let(:client) { FuelSDK::Client.new }
6
+ subject {client}
7
+
8
+ it 'has inflector support' do
9
+ expect('Definitions'.singularize).to eq 'Definition'
10
+ end
11
+
12
+ describe '#create_action_message' do
13
+ it 'returns message' do
14
+ expect(subject.create_action_message('Definitions', 'QueryDefinition', [{'ObjectID' => 1}], 'start'))
15
+ .to eq(
16
+ {
17
+ 'Action' => 'start',
18
+ 'Definitions' => {
19
+ 'Definition' => [{'ObjectID' => 1}],
20
+ :attributes! => {
21
+ 'Definition' => { 'xsi:type' => 'tns:QueryDefinition'}
22
+ }
23
+ }
24
+ }
25
+ )
26
+ end
27
+
28
+ it 'standardizes properties to an array' do
29
+ expect(subject.create_action_message('Definitions', 'QueryDefinition', {'ObjectID' => 1}, 'start'))
30
+ .to eq(
31
+ {
32
+ 'Action' => 'start',
33
+ 'Definitions' => {
34
+ 'Definition' => [{'ObjectID' => 1}],
35
+ :attributes! => {
36
+ 'Definition' => { 'xsi:type' => 'tns:QueryDefinition'}
37
+ }
38
+ }
39
+ }
40
+ )
41
+ end
42
+ end
43
+
44
+ describe '#soap_perform' do
45
+ it 'starts a defined query' do
46
+ subject.should_receive(:create_action_message)
47
+ .with('Definitions', 'QueryDefinition', [{'ObjectID' => 1}], 'start')
48
+ .and_return 'Do It'
49
+ subject.should_receive(:soap_request).with(:perform, 'Do It')
50
+ subject.soap_perform 'QueryDefinition', [{'ObjectID' => 1}], 'start'
51
+ end
52
+ end
53
+ end
@@ -6,11 +6,11 @@ describe FuelSDK::Soap do
6
6
 
7
7
  subject { client }
8
8
 
9
- it { should respond_to(:soap_get) }
10
9
  it { should respond_to(:soap_post) }
11
10
  it { should respond_to(:soap_patch) }
12
11
  it { should respond_to(:soap_delete) }
13
12
  it { should respond_to(:soap_describe) }
13
+ it { should respond_to(:soap_perform) }
14
14
 
15
15
  it { should respond_to(:header) }
16
16
  it { should_not respond_to(:header=) }
@@ -62,8 +62,7 @@ describe FuelSDK::Soap do
62
62
  client.stub(:soap_request) do |action, message|
63
63
  [action, message]
64
64
  end
65
-
66
- client.stub_chain(:soap_describe,:editable)
65
+ client.should_receive(:get_editable_properties)
67
66
  .and_return(['First Name', 'Last Name', 'Gender'])
68
67
  client
69
68
  }
@@ -12,3 +12,51 @@ RSpec.configure do |config|
12
12
  # Use the specified formatter
13
13
  config.formatter = :documentation
14
14
  end
15
+
16
+ shared_examples_for 'Response Object' do
17
+ it { should respond_to(:code) }
18
+ it { should respond_to(:message) }
19
+ it { should respond_to(:results) }
20
+ it { should respond_to(:request_id) }
21
+ it { should respond_to(:body) }
22
+ it { should respond_to(:raw) }
23
+ it { should respond_to(:more) }
24
+ it { should respond_to(:more?) }
25
+ it { should respond_to(:success) }
26
+ it { should respond_to(:success?) }
27
+ it { should respond_to(:status) }
28
+ it { should respond_to(:continue) }
29
+ end
30
+
31
+ # Everything will be readable so test for shared from Read behavior
32
+ shared_examples_for 'Soap Read Object' do
33
+ # begin backwards compat
34
+ it { should respond_to :props= }
35
+ it { should respond_to :authStub= }
36
+ # end
37
+ it { should respond_to :id }
38
+ it { should respond_to :properties }
39
+ it { should respond_to :client }
40
+ it { should respond_to :filter }
41
+ it { should respond_to :info }
42
+ it { should respond_to :get }
43
+ end
44
+
45
+ shared_examples_for 'Soap CUD Object' do
46
+ it { should respond_to :post }
47
+ it { should respond_to :patch }
48
+ it { should respond_to :delete }
49
+ end
50
+
51
+ shared_examples_for 'Soap Object' do
52
+ it_behaves_like 'Soap Read Object'
53
+ it_behaves_like 'Soap CUD Object'
54
+ end
55
+
56
+ shared_examples_for 'Soap Read Only Object' do
57
+ it_behaves_like 'Soap Read Object'
58
+ it { should_not respond_to :post }
59
+ it { should_not respond_to :patch }
60
+ it { should_not respond_to :delete }
61
+ end
62
+
@@ -11,28 +11,59 @@ describe FuelSDK::Targeting do
11
11
  it { should respond_to(:post) }
12
12
  it { should respond_to(:patch) }
13
13
  it { should respond_to(:delete) }
14
- it { should respond_to(:access_token) }
14
+ it { should respond_to(:auth_token) }
15
+
16
+ let(:response) {
17
+ rsp = double(FuelSDK::HTTPResponse)
18
+ rsp.stub(:success?).and_return(true)
19
+ rsp.stub(:[]).with('url').and_return('S#.authentication.target')
20
+ rsp
21
+ }
22
+
23
+ let(:client) {
24
+ Class.new.new.extend(FuelSDK::Targeting)
25
+ }
15
26
 
16
27
  describe '#determine_stack' do
17
- let(:client) { c = Class.new.new.extend(FuelSDK::Targeting)
18
- c.stub(:access_token).and_return('open_sesame')
19
- c.stub(:get)
20
- .with('https://www.exacttargetapis.com/platform/v1/endpoints/soap',{'params'=>{'access_token'=>'open_sesame'}})
21
- .and_return({'url' => 'S#.authentication.target'})
22
- c
23
- }
24
- it 'sets @endpoint' do
25
- expect(client.send(:determine_stack)).to eq 'S#.authentication.target'
28
+ describe 'without auth_token' do
29
+ it 'calls refresh' do
30
+ client.stub(:refresh) {
31
+ client.instance_variable_set('@auth_token', 'open_sesame')
32
+ }
33
+ client.stub(:get)
34
+ .with('https://www.exacttargetapis.com/platform/v1/endpoints/soap',
35
+ {'params'=>{'access_token'=>'open_sesame'}})
36
+ .and_return(response)
37
+ end
38
+ end
39
+
40
+ describe 'with valid auth_token' do
41
+ before :each do
42
+ client.should_receive(:auth_token).twice.and_return('open_sesame')
43
+ end
44
+
45
+ it 'when successful returns endpoint' do
46
+ client.stub(:get)
47
+ .with('https://www.exacttargetapis.com/platform/v1/endpoints/soap',
48
+ {'params'=>{'access_token'=>'open_sesame'}})
49
+ .and_return(response)
50
+ expect(client.send(:determine_stack)).to eq 'S#.authentication.target'
51
+ end
52
+
53
+ it 'raises error on unsuccessful responses' do
54
+ client.stub(:get).and_return{
55
+ rsp = double(FuelSDK::HTTPResponse)
56
+ rsp.stub(:success?).and_return(false)
57
+ rsp
58
+ }
59
+ expect{ client.send(:determine_stack) }.to raise_error 'Unable to determine stack'
60
+ end
26
61
  end
27
62
  end
28
63
 
29
64
  describe '#endpoint' do
30
- let(:client) { c = Class.new.new.extend(FuelSDK::Targeting)
31
- c.stub(:get).and_return({'url' => 'S#.authentication.target'})
32
- c
33
- }
34
-
35
65
  it 'calls determine_stack to find target' do
66
+ client.should_receive(:determine_stack).and_return('S#.authentication.target')
36
67
  expect(client.endpoint).to eq 'S#.authentication.target'
37
68
  end
38
69
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fuelsdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-08-29 00:00:00.000000000 Z
13
+ date: 2013-09-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -140,6 +140,22 @@ dependencies:
140
140
  - - ~>
141
141
  - !ruby/object:Gem::Version
142
142
  version: 0.1.6
143
+ - !ruby/object:Gem::Dependency
144
+ name: activesupport
145
+ requirement: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ~>
149
+ - !ruby/object:Gem::Version
150
+ version: 3.2.8
151
+ type: :runtime
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ~>
157
+ - !ruby/object:Gem::Version
158
+ version: 3.2.8
143
159
  description: Fuel SDK for Ruby
144
160
  email: []
145
161
  executables: []
@@ -147,6 +163,7 @@ extensions: []
147
163
  extra_rdoc_files: []
148
164
  files:
149
165
  - .gitignore
166
+ - ChangeLog.md
150
167
  - Gemfile
151
168
  - Guardfile
152
169
  - README.md
@@ -180,12 +197,17 @@ files:
180
197
  - samples/sample-unsubevent.rb
181
198
  - samples/sample_helper.rb
182
199
  - spec/client_spec.rb
200
+ - spec/fuelsdk_response_spec.rb
183
201
  - spec/helper_funcs_spec.rb
184
202
  - spec/http_request_spec.rb
185
- - spec/objects_helper_spec.rb
186
203
  - spec/objects_spec.rb
187
204
  - spec/rest_spec.rb
188
- - spec/soap_spec.rb
205
+ - spec/soap/configure_spec.rb
206
+ - spec/soap/cud_spec.rb
207
+ - spec/soap/describe_error_spec.rb
208
+ - spec/soap/get_spec.rb
209
+ - spec/soap/perform_spec.rb
210
+ - spec/soap/soap_spec.rb
189
211
  - spec/spec_helper.rb
190
212
  - spec/targeting_spec.rb
191
213
  homepage: https://code.exacttarget.com/sdks
@@ -233,11 +255,16 @@ test_files:
233
255
  - samples/sample-unsubevent.rb
234
256
  - samples/sample_helper.rb
235
257
  - spec/client_spec.rb
258
+ - spec/fuelsdk_response_spec.rb
236
259
  - spec/helper_funcs_spec.rb
237
260
  - spec/http_request_spec.rb
238
- - spec/objects_helper_spec.rb
239
261
  - spec/objects_spec.rb
240
262
  - spec/rest_spec.rb
241
- - spec/soap_spec.rb
263
+ - spec/soap/configure_spec.rb
264
+ - spec/soap/cud_spec.rb
265
+ - spec/soap/describe_error_spec.rb
266
+ - spec/soap/get_spec.rb
267
+ - spec/soap/perform_spec.rb
268
+ - spec/soap/soap_spec.rb
242
269
  - spec/spec_helper.rb
243
270
  - spec/targeting_spec.rb
@@ -1,32 +0,0 @@
1
-
2
- # Everything will be readable so test for shared from Read behavior
3
- shared_examples_for 'Soap Read Object' do
4
- # begin backwards compat
5
- it { should respond_to :props= }
6
- it { should respond_to :authStub= }
7
- # end
8
- it { should respond_to :id }
9
- it { should respond_to :properties }
10
- it { should respond_to :client }
11
- it { should respond_to :filter }
12
- it { should respond_to :info }
13
- it { should respond_to :get }
14
- end
15
-
16
- shared_examples_for 'Soap CUD Object' do
17
- it { should respond_to :post }
18
- it { should respond_to :patch }
19
- it { should respond_to :delete }
20
- end
21
-
22
- shared_examples_for 'Soap Object' do
23
- it_behaves_like 'Soap Read Object'
24
- it_behaves_like 'Soap CUD Object'
25
- end
26
-
27
- shared_examples_for 'Soap Read Only Object' do
28
- it_behaves_like 'Soap Read Object'
29
- it { should_not respond_to :post }
30
- it { should_not respond_to :patch }
31
- it { should_not respond_to :delete }
32
- end