amee 4.3.2 → 4.4.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.
Files changed (63) hide show
  1. data/CHANGELOG.txt +6 -0
  2. data/Gemfile +7 -1
  3. data/Rakefile +2 -0
  4. data/VERSION +1 -1
  5. data/amee.gemspec +57 -6
  6. data/amee_test_credentials.example.yml +11 -0
  7. data/cassettes/AMEE_Connection/v2/raising_unhandled_errors.yml +36 -0
  8. data/cassettes/AMEE_Connection/v3/retries/502.yml +36 -0
  9. data/cassettes/AMEE_Connection/v3/retries/503.yml +36 -0
  10. data/cassettes/AMEE_Connection/v3/retries/504.yml +36 -0
  11. data/cassettes/AMEE_Connection/v3/retries/AMEE_TimeOut.yml +36 -0
  12. data/cassettes/AMEE_Connection/v3/should_be_able_to_get_from_meta_server.yml +30 -0
  13. data/cassettes/AMEE_Connection/v3/should_be_able_to_handle_failed_gets_from_meta_server.yml +30 -0
  14. data/cassettes/AMEE_Connection/v3/should_be_able_to_post_to_meta_server.yml +59 -0
  15. data/cassettes/AMEE_Connection/v3/should_have_a_connection_to_meta_server.yml +36 -0
  16. data/cassettes/AMEE_Connection/v3/should_login_and_know_the_path_to_the_server.yml +36 -0
  17. data/cassettes/AMEE_Connection_Caching_Off/authenticating.yml +36 -0
  18. data/cassettes/AMEE_Connection_Caching_Off/first_request.yml +40 -0
  19. data/cassettes/AMEE_Connection_Caching_Off/second_request.yml +40 -0
  20. data/cassettes/AMEE_Connection_Caching_On/authenticating.yml +36 -0
  21. data/cassettes/AMEE_Connection_Caching_On/first_request.yml +40 -0
  22. data/cassettes/AMEE_Connection_Caching_clear_all/second_request.yml +40 -0
  23. data/cassettes/AMEE_Connection_Caching_clear_manually/second_request.yml +40 -0
  24. data/cassettes/AMEE_Connection_Caching_further_down_tree/second_request.yml +79 -0
  25. data/cassettes/AMEE_Connection_with_authentication/handling_404s.yml +69 -0
  26. data/cassettes/AMEE_Connection_with_authentication/hitting_private_urls.yml +75 -0
  27. data/cassettes/AMEE_Connection_with_authentication/raising_errors_if_permission_denied.yml +69 -0
  28. data/cassettes/AMEE_Connection_with_authentication/should_re-authenticate_and_refresh_authtoken_when_authtoken_expires.yml +104 -0
  29. data/cassettes/AMEE_Connection_with_authentication/should_refresh_authtoken_when_authtoken_is_changed.yml +114 -0
  30. data/cassettes/AMEE_Connection_with_authentication/using_a_v1_key.yml +71 -0
  31. data/cassettes/AMEE_Connection_with_authentication/using_a_v2_key/detects_the_API_version_for_JSON.yml +36 -0
  32. data/cassettes/AMEE_Connection_with_authentication/using_a_v2_key/detects_the_API_version_for_XML.yml +36 -0
  33. data/cassettes/AMEE_Connection_with_authentication_doing_write-requests.yml +75 -0
  34. data/cassettes/AMEE_Connection_with_authentication_doing_write-requests/working_with_an_existing_profile/deleting_existing_items.yml +108 -0
  35. data/cassettes/AMEE_Connection_with_authentication_doing_write-requests/working_with_an_existing_profile/sending_updates_to_existing_items.yml +108 -0
  36. data/cassettes/AMEE_Connection_with_bad_authentication_information/hitting_a_private_url.yml +29 -0
  37. data/cassettes/AMEE_Connection_with_bad_authentication_information/hitting_a_public_url.yml +32 -0
  38. data/cassettes/AMEE_Connection_with_incorrect_server_name.yml +16 -0
  39. data/cassettes/AMEE_Connection_with_retry_enabled.yml +36 -0
  40. data/lib/amee/connection.rb +234 -110
  41. data/lib/amee/data_category.rb +2 -2
  42. data/lib/amee/data_item.rb +45 -2
  43. data/lib/amee/data_item_value.rb +1 -1
  44. data/lib/amee/exceptions.rb +5 -2
  45. data/lib/amee/parse_helper.rb +2 -0
  46. data/lib/amee/profile_item.rb +6 -2
  47. data/lib/amee/v3/connection.rb +77 -70
  48. data/lib/amee/v3/item_value_definition.rb +1 -1
  49. data/lib/amee/v3/return_value_definition.rb +1 -1
  50. data/spec/cache_spec.rb +107 -48
  51. data/spec/connection_spec.rb +224 -183
  52. data/spec/data_item_spec.rb +12 -0
  53. data/spec/data_item_value_history_spec.rb +4 -4
  54. data/spec/data_item_value_spec.rb +2 -2
  55. data/spec/fixtures/AD63A83B4D41.json +1 -1
  56. data/spec/fixtures/AD63A83B4D41.xml +1 -1
  57. data/spec/profile_item_spec.rb +14 -10
  58. data/spec/spec_helper.rb +29 -0
  59. data/spec/v3/connection_spec.rb +77 -65
  60. data/spec/v3/item_value_definition_spec.rb +1 -0
  61. data/spec/v3/return_value_definition_spec.rb +1 -1
  62. metadata +140 -24
  63. data/Gemfile.lock +0 -63
@@ -168,8 +168,8 @@ module AMEE
168
168
  # Send data to path
169
169
  options[:newObjectType] = "DC"
170
170
  response = connection.post(path, options)
171
- if response['Location']
172
- location = response['Location'].match("https??://.*?(/.*)")[1]
171
+ if response.headers_hash.has_key?('Location') && response.headers_hash['Location']
172
+ location = response.headers_hash['Location'].match("https??://.*?(/.*)")[1]
173
173
  else
174
174
  category = Category.parse(connection, response.body)
175
175
  location = category.full_path
@@ -14,6 +14,8 @@ module AMEE
14
14
  @item_definition_uid = data[:item_definition]
15
15
  @total_amount = data[:total_amount]
16
16
  @total_amount_unit = data[:total_amount_unit]
17
+ @amounts = data[:amounts] || []
18
+ @notes = data[:notes] || []
17
19
  @start_date = data[:start_date]
18
20
  @category_uid = data[:category_uid]
19
21
  super
@@ -24,6 +26,8 @@ module AMEE
24
26
  attr_reader :label
25
27
  attr_reader :total_amount
26
28
  attr_reader :total_amount_unit
29
+ attr_reader :amounts
30
+ attr_reader :notes
27
31
  attr_reader :start_date
28
32
  attr_reader :category_uid
29
33
  attr_reader :item_definition_uid
@@ -55,6 +59,28 @@ module AMEE
55
59
  data[:total_amount] = doc['amountPerMonth'] rescue nil
56
60
  data[:total_amount_unit] = "kg/month"
57
61
  end
62
+ # Read amounts
63
+ if doc['amounts']
64
+ if doc['amounts']['amount']
65
+ data[:amounts] = doc['amounts']['amount'].map do |item|
66
+ {
67
+ :type => item['type'],
68
+ :value => item['value'].to_f,
69
+ :unit => item['unit'],
70
+ :per_unit => item['perUnit'],
71
+ :default => (item['default'] == 'true'),
72
+ }
73
+ end
74
+ end
75
+ if doc['amounts']['note']
76
+ data[:notes] = doc['amounts']['note'].map do |item|
77
+ {
78
+ :type => item['type'],
79
+ :value => item['value'],
80
+ }
81
+ end
82
+ end
83
+ end
58
84
  # Get values
59
85
  data[:values] = []
60
86
  doc['dataItem']['itemValues'].each do |value|
@@ -103,6 +129,23 @@ module AMEE
103
129
  data[:total_amount] = REXML::XPath.first(doc, '/Resources/DataItemResource/AmountPerMonth').text.to_f rescue nil
104
130
  data[:total_amount_unit] = "kg/month"
105
131
  end
132
+ data[:amounts] = []
133
+ REXML::XPath.each(doc,'/Resources/DataItemResource/Amounts/Amount') do |amount|
134
+ amount_data = {}
135
+ amount_data[:type] = amount.attribute('type').value
136
+ amount_data[:value] = amount.text.to_f
137
+ amount_data[:unit] = amount.attribute('unit').value
138
+ amount_data[:per_unit] = amount.attribute('perUnit').value if amount.attribute('perUnit')
139
+ amount_data[:default] = (amount.attribute('default').value == 'true') if amount.attribute('default')
140
+ data[:amounts] << amount_data
141
+ end
142
+ data[:notes] = []
143
+ REXML::XPath.each(doc,'/Resources/DataItemResource/Amounts/Note') do |note|
144
+ note_data = {}
145
+ note_data[:type] = note.attribute('type').value
146
+ note_data[:value] = note.text
147
+ data[:notes] << note_data
148
+ end
106
149
  # Get values
107
150
  data[:values] = []
108
151
  REXML::XPath.each(doc, '/Resources/DataItemResource/DataItem/ItemValues/ItemValue') do |value|
@@ -188,8 +231,8 @@ module AMEE
188
231
  options[:newObjectType] = "DI"
189
232
  # Send data to path
190
233
  response = connection.post(path, options)
191
- if response['Location']
192
- location = response['Location'].match("https??://.*?(/.*)")[1]
234
+ if response.headers_hash.has_key?('Location') && response.headers_hash['Location']
235
+ location = response.headers_hash['Location'].match("https??://.*?(/.*)")[1]
193
236
  else
194
237
  category = Category.parse(connection, response.body)
195
238
  location = category.full_path + "/" + category.items[0][:path]
@@ -175,7 +175,7 @@ module AMEE
175
175
  end
176
176
 
177
177
  response = data_item.connection.post(data_item.full_path, options)
178
- location = response['Location'].match("https??://.*?(/.*)")[1]
178
+ location = response.headers_hash['Location'].match("https??://.*?(/.*)")[1]
179
179
  if get_item == true
180
180
  get_options = {}
181
181
  get_options[:format] = format if format
@@ -20,7 +20,7 @@ module AMEE
20
20
  end
21
21
  end
22
22
  end
23
-
23
+
24
24
  class BadRequest < Exception
25
25
  end
26
26
 
@@ -32,7 +32,10 @@ module AMEE
32
32
 
33
33
  class ConnectionFailed < Exception
34
34
  end
35
-
35
+
36
+ class TimeOut < Exception
37
+ end
38
+
36
39
  class NotFound < Exception
37
40
  end
38
41
 
@@ -2,6 +2,7 @@
2
2
  # Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
3
3
 
4
4
  module ParseHelper
5
+
5
6
  def x(xpath,options = {})
6
7
  doc = options[:doc] || @doc
7
8
  preamble = options[:meta] == true ? metaxmlpathpreamble : xmlpathpreamble
@@ -36,6 +37,7 @@ module ParseHelper
36
37
  def xmlpathpreamble
37
38
  ''
38
39
  end
40
+
39
41
  def load_xml_doc(xml)
40
42
  doc = Nokogiri.XML(xml) { |config| config.strict }
41
43
  doc.remove_namespaces!
@@ -1,6 +1,9 @@
1
1
  # Copyright (C) 2008-2011 AMEE UK Ltd. - http://www.amee.com
2
2
  # Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
3
3
 
4
+ require 'active_support/core_ext/hash/conversions'
5
+ require 'active_support/inflector'
6
+
4
7
  module AMEE
5
8
  module Profile
6
9
  class Item < AMEE::Profile::Object
@@ -314,8 +317,8 @@ module AMEE
314
317
  response = connection.post(path, options)
315
318
  # Parse response
316
319
  category = response.body.empty? ? nil : Category.parse(connection, response.body, options)
317
- if response['Location']
318
- location = response['Location'].match("https??://.*?(/.*)")[1]
320
+ if response.headers_hash.has_key?('Location') && response.headers_hash['Location']
321
+ location = response.headers_hash['Location'].match("https??://.*?(/.*)")[1]
319
322
  else
320
323
  location = category.full_path + "/" + category.items[0][:path]
321
324
  end
@@ -335,6 +338,7 @@ module AMEE
335
338
  values << v.merge(:path => k.to_s)
336
339
  end
337
340
  item[:values] = values
341
+ item[:path] = category.path + '/' + item[:path]
338
342
  return AMEE::Profile::Item.new(item)
339
343
  else
340
344
  get_options = {}
@@ -6,61 +6,97 @@ require 'active_support/core_ext/string'
6
6
  module AMEE
7
7
  class Connection
8
8
 
9
+ # API version used in URLs
9
10
  def self.api_version
10
11
  '3'
11
12
  end
12
-
13
- def v3_connection
14
- @v3_http ||= begin
15
- @v3_http = Net::HTTP.new(v3_hostname, @port)
16
- if @ssl == true
17
- @v3_http.use_ssl = true
18
- if File.exists? RootCA
19
- @v3_http.ca_file = RootCA
20
- @v3_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
21
- @v3_http.verify_depth = 5
22
- end
23
- end
24
- @v3_http.set_debug_output($stdout) if @debug
25
- @v3_http
26
- end
27
- end
13
+
14
+ # Perform a GET request
15
+ # options hash should contain query parameters
28
16
  def v3_get(path, options = {})
29
- # Create URL parameters
30
- unless options.empty?
31
- path += "?" + options.map { |x| "#{CGI::escape(x[0].to_s)}=#{CGI::escape(x[1].to_s)}" }.join('&')
32
- end
33
- cache(path) { v3_request(Net::HTTP::Get.new(path)) }
17
+ # Create request parameters
18
+ get_params = {
19
+ :method => "get"
20
+ }
21
+ get_params[:params] = options unless options.empty?
22
+ # Send request (with caching)
23
+ v3_do_request(get_params, path, :cache => true)
34
24
  end
25
+
26
+ # Perform a PUT request
27
+ # options hash should contain request body parameters
35
28
  def v3_put(path, options = {})
29
+ # Expire cached objects from parent on down
36
30
  expire_matching "#{parent_path(path)}.*"
37
- put = Net::HTTP::Put.new(path)
38
- if options[:body]
39
- put.body = options[:body]
40
- put['Content-Type'] = content_type :xml if options[:body].is_xml?
41
- put['Content-Type'] = content_type :json if options[:body].is_json?
42
- else
43
- put.set_form_data(options)
31
+ # Create request parameters
32
+ put_params = {
33
+ :method => "put",
34
+ :body => options[:body] ? options[:body] : form_encode(options)
35
+ }
36
+ if options[:content_type]
37
+ put_params[:headers] = {
38
+ :'Content-Type' => content_type(options[:content_type])
39
+ }
44
40
  end
45
- v3_request put
41
+ # Request
42
+ v3_do_request(put_params, path)
46
43
  end
44
+
45
+ # Perform a POST request
46
+ # options hash should contain request body parameters
47
+ # It can also contain a :returnobj parameter which will cause
48
+ # a full reponse object to be returned instead of just the body
47
49
  def v3_post(path, options = {})
50
+ # Expire cached objects from here on down
48
51
  expire_matching "#{raw_path(path)}.*"
49
- post = Net::HTTP::Post.new(path)
50
- returnobj=options.delete(:returnobj) || false
51
- post.set_form_data(options)
52
- v3_request post,returnobj
52
+ # Get 'return full response object' flag
53
+ return_obj = options.delete(:returnobj) || false
54
+ # Create request parameters
55
+ post_params = {
56
+ :method => "post",
57
+ :body => form_encode(options)
58
+ }
59
+ if options[:content_type]
60
+ post_params[:headers] = {
61
+ :'Content-Type' => content_type(options[:content_type])
62
+ }
63
+ end
64
+ # Request
65
+ v3_do_request(post_params, path, :return_obj => return_obj)
53
66
  end
54
- def v3_delete(path, options = {})
67
+
68
+ # Perform a POST request
69
+ def v3_delete(path)
70
+ # Expire cached objects from here on down
55
71
  expire_matching "#{parent_path(path)}.*"
56
- delete = Net::HTTP::Delete.new(path)
57
- v3_request delete
58
- end
59
- def v3_auth
60
- # now the same as v2, i.e.
61
- [@username,@password]
72
+ # Create request parameters
73
+ delete_params = {
74
+ :method => "delete"
75
+ }
76
+ # Request
77
+ v3_do_request(delete_params, path)
62
78
  end
79
+
63
80
  private
81
+
82
+ # Default request parameters
83
+ def v3_defaults
84
+ {
85
+ :verbose => DEBUG,
86
+ :follow_location => true,
87
+ :username => @username,
88
+ :password => @password
89
+ }
90
+ end
91
+
92
+ # Wrap up parameters into a request and execute it
93
+ def v3_do_request(params, path, options = {})
94
+ req = Typhoeus::Request.new("https://#{v3_hostname}#{path}", v3_defaults.merge(params))
95
+ response = do_request(req, :xml, options)
96
+ options[:return_obj]==true ? response : response.body
97
+ end
98
+
99
+ # Work out v3 hostname corresponding to v2 hostname
64
100
  def v3_hostname
65
101
  unless @server.starts_with?("platform-api-")
66
102
  if @server.starts_with?("platform-")
@@ -72,35 +108,6 @@ module AMEE
72
108
  @server
73
109
  end
74
110
  end
75
- def v3_request(req,returnobj=false)
76
- # Open HTTP connection
77
- v3_connection.start
78
- # Set auth
79
- req.basic_auth *v3_auth
80
- # Do request
81
- timethen=Time.now
82
- response = send_request(v3_connection, req, :xml)
83
- Logger.log.debug("Requested #{req.class} at #{req.path} with #{req.body}, taking #{(Time.now-timethen)*1000} miliseconds")
84
- v3_response_ok? response, req
85
- returnobj ? response : response.body
86
- ensure
87
- v3_connection.finish if v3_connection.started?
88
- end
89
-
90
- def v3_response_ok?(response, request)
91
- case response.code
92
- when '200', '201', '204'
93
- return true
94
- when '404'
95
- raise AMEE::NotFound.new("The URL was not found on the server.\nRequest: #{request.method} #{request.path}")
96
- when '403'
97
- raise AMEE::PermissionDenied.new("You do not have permission to perform the requested operation.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
98
- when '401'
99
- raise AMEE::AuthFailed.new("Authentication failed. Please check your username and password.")
100
- when '400'
101
- raise AMEE::BadRequest.new("Bad request. This is probably due to malformed input data.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
102
- end
103
- raise AMEE::UnknownError.new("An error occurred while talking to AMEE: HTTP response code #{response.code}.\nRequest: #{request.method} #{request.path}\n#{request.body}\Response: #{response.body}")
104
- end
111
+
105
112
  end
106
113
  end
@@ -50,7 +50,7 @@ EOF
50
50
  </Usages>
51
51
  </ItemValueDefinition>
52
52
  EOF
53
- {:body => str}
53
+ {:body => str, :content_type => :xml}
54
54
  end
55
55
  def usages
56
56
  @usages.keys
@@ -179,7 +179,7 @@ module AMEE
179
179
 
180
180
  options.merge!(:returnobj=>true)
181
181
  response = connection.v3_post("/#{AMEE::Connection.api_version}/definitions/#{itemdefuid}/returnvalues", options)
182
- return ReturnValueDefinition.load(connection,itemdefuid , response['Location'].split('/')[7])
182
+ return ReturnValueDefinition.load(connection,itemdefuid , response.headers_hash['Location'].split('/')[7])
183
183
  rescue
184
184
  raise AMEE::BadData.new("Couldn't create ReturnValueDefinition. Check that your information is correct.\n#{response}")
185
185
  end
@@ -9,14 +9,35 @@ describe AMEE::Connection do
9
9
  describe 'without caching' do
10
10
 
11
11
  it "doesn't cache GET requests" do
12
- flexmock(Net::HTTP).new_instances do |mock|
13
- mock.should_receive(:start => nil)
14
- mock.should_receive(:request).twice.and_return(flexmock(:code => '200', :body => fixture('data_home_energy_quantity.xml')))
15
- mock.should_receive(:finish => nil)
16
- end
17
- @connection = AMEE::Connection.new("server.example.com", "username", "password")
18
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
19
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
12
+ VCR.use_cassette("AMEE_Connection_Caching_Off/logging_in") do
13
+ @connection = AMEE::Connection.new("stage.amee.com", "amee_ruby_vcr_v2", "8nkj8rm7")
14
+ end
15
+
16
+ VCR.use_cassette("AMEE_Connection_Caching_Off/authenticating") do
17
+ @connection.authenticate
18
+ end
19
+
20
+ VCR.use_cassette("AMEE_Connection_Caching_Off/first_request") do
21
+ @first_response = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
22
+ end
23
+
24
+ VCR.use_cassette("AMEE_Connection_Caching_Off/second_request") do
25
+ @second_response = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
26
+ end
27
+
28
+ # We're checking to see if we actually have a returned object.
29
+ # If there's no response in the VCR cassette, then we can't build the object
30
+ # to check if it responds to the methods below
31
+ @first_response.should respond_to(:uid)
32
+ @first_response.should respond_to(:name)
33
+
34
+ # Likewise with the second response. There needs to be a yaml file to read from to
35
+ # build the object.
36
+ File.exists?('cassettes/AMEE_Connection_Caching_Off/second_request.yml').should be true
37
+ # Then we check for the same content to see we can build the object needed.
38
+ @second_response.should respond_to(:uid)
39
+ @second_response.should respond_to(:name)
40
+
20
41
  end
21
42
 
22
43
  end
@@ -24,61 +45,86 @@ describe AMEE::Connection do
24
45
  describe 'with caching' do
25
46
 
26
47
  def setup_connection
27
- @connection = AMEE::Connection.new("server.example.com", "username", "password", :cache => :memory_store)
48
+ VCR.use_cassette("AMEE_Connection_Caching_On/logging_in") do
49
+ @connection = AMEE::Connection.new("stage.amee.com", "amee_ruby_vcr_v2", "8nkj8rm7", :cache => :memory_store)
50
+ end
51
+
52
+ VCR.use_cassette("AMEE_Connection_Caching_On/authenticating") do
53
+ @connection.authenticate
54
+ end
55
+
56
+ VCR.use_cassette("AMEE_Connection_Caching_On/first_request") do
57
+ @first_response = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
58
+ end
28
59
  end
29
60
 
30
61
  it "caches GET requests" do
31
- flexmock(Net::HTTP).new_instances do |mock|
32
- mock.should_receive(:start => nil)
33
- mock.should_receive(:request).once.and_return(OpenStruct.new(:code => '200', :body => fixture('data_home_energy_quantity.xml')))
34
- mock.should_receive(:finish => nil)
35
- end
62
+
36
63
  setup_connection
37
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
38
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
64
+
65
+ VCR.use_cassette("AMEE_Connection_Caching_On/second_request") do
66
+ @second_response = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
67
+ end
68
+
69
+ # We're checking to see if we actually have a returned object.
70
+ # If there's no response in the VCR cassette, then we can't build the object
71
+ # to check if it responds to the methods below
72
+ @first_response.should respond_to(:uid)
73
+ @first_response.should respond_to(:name)
74
+
75
+ # Likewise with the second response. When caching is on, we don't want to see a second
76
+ # request being recorded, but we still want to see the object being built
77
+ File.exists?('cassettes/AMEE_Connection_Caching_On/second_request.yml').should be false
78
+ # Then we check for the same content to see we can build the object needed.
79
+ @second_response.should respond_to(:uid)
80
+ @second_response.should respond_to(:name)
39
81
  end
40
82
 
41
83
  it "allows complete cache clear" do
42
- flexmock(Net::HTTP).new_instances do |mock|
43
- mock.should_receive(:start => nil)
44
- mock.should_receive(:request).twice.and_return(OpenStruct.new(:code => '200', :body => fixture('data_home_energy_quantity.xml')))
45
- mock.should_receive(:finish => nil)
46
- end
84
+
47
85
  setup_connection
48
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
49
86
  @connection.expire_all
50
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
87
+
88
+ VCR.use_cassette("AMEE_Connection_Caching_clear_all/second_request") do
89
+ @second_response = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
90
+ end
91
+
92
+ File.exists?('cassettes/AMEE_Connection_Caching_clear_all/second_request.yml').should be true
93
+ @second_response.should respond_to(:uid)
94
+ @second_response.should respond_to(:name)
51
95
  end
52
96
 
53
97
  it "allows manual cache expiry for objects" do
54
- flexmock(Net::HTTP).new_instances do |mock|
55
- mock.should_receive(:start => nil)
56
- mock.should_receive(:request).twice.and_return(OpenStruct.new(:code => '200', :body => fixture('data_home_energy_quantity.xml')))
57
- mock.should_receive(:finish => nil)
58
- end
59
98
  setup_connection
60
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
61
- c.expire_cache
62
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
63
- end
99
+ @first_response.expire_cache
64
100
 
65
- it "object expiry invalidates objectes further down the tree" do
66
- flexmock(Net::HTTP).new_instances do |mock|
67
- mock.should_receive(:start => nil)
68
- mock.should_receive(:request).once.and_return(OpenStruct.new(:code => '200', :body => fixture('data_home_energy_quantity.xml')))
69
- mock.should_receive(:request).twice.and_return(OpenStruct.new(:code => '200', :body => fixture('data_home_energy_quantity_biodiesel.xml')))
70
- mock.should_receive(:finish => nil)
101
+ VCR.use_cassette("AMEE_Connection_Caching_clear_manually/second_request") do
102
+ @second_response = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
71
103
  end
104
+
105
+ File.exists?('cassettes/AMEE_Connection_Caching_clear_manually/second_request.yml').should be true
106
+ @second_response.should respond_to(:uid)
107
+ @second_response.should respond_to(:name)
108
+ end
109
+
110
+ it "object expiry invalidates objects further down the tree" do
72
111
  setup_connection
73
- c = AMEE::Data::Category.get(@connection, '/data/home/energy/quantity')
74
- i = c.item :label => 'biodiesel'
75
- c.expire_cache
76
- i = c.item :label => 'biodiesel'
112
+
113
+ VCR.use_cassette("AMEE_Connection_Caching_further_down_tree/second_request") do
114
+ @second_response = @first_response.item :label => 'biodiesel'
115
+ @first_response.expire_cache
116
+ @third_response = @first_response.item :label => 'biodiesel'
117
+ end
118
+
119
+ File.exists?('cassettes/AMEE_Connection_Caching_further_down_tree/second_request.yml').should be true
120
+ @second_response.label.should == "biodiesel"
121
+ @third_response.label.should == "biodiesel"
122
+
77
123
  end
78
124
 
79
125
  it "removes special characters from cache keys, include slashes" do
80
126
  setup_connection
81
- @connection.send(:cache_key, "/%cache/$4/%20test").should eql 'server.example.com_cache_4_20test'
127
+ @connection.send(:cache_key, "/%cache/$4/%20test").should eql 'stage.amee.com_cache_4_20test'
82
128
  end
83
129
 
84
130
  it "trims cache keys to correct length for filenames, allowing for lock extension" do
@@ -99,14 +145,12 @@ describe AMEE::Connection do
99
145
  describe 'and automatic invalidation' do
100
146
 
101
147
  def test_invalidation_sequence(interactions)
102
- flexmock(Net::HTTP).new_instances do |mock|
103
- mock.should_receive(:start => nil)
148
+ setup_connection
149
+ flexmock(@connection) do |mock|
104
150
  interactions.each do |path, action, result|
105
- mock.should_receive(:request).once.and_return(OpenStruct.new(:code => '200', :body => path)) if result
151
+ mock.should_receive(:run_request).once.and_return(OpenStruct.new(:code => '200', :body => path)) if result
106
152
  end
107
- mock.should_receive(:finish => nil)
108
153
  end
109
- setup_connection
110
154
  interactions.each do |path, action, result|
111
155
  if action
112
156
  @connection.send(action, path).body.should == path
@@ -115,14 +159,18 @@ describe AMEE::Connection do
115
159
  end
116
160
 
117
161
  it "handles PUT requests" do
162
+ VCR.use_cassette("AMEE_Connection_Caching/automatic_invalidation_for_put") do
118
163
  test_invalidation_sequence([
164
+ # Fill the cache
119
165
  ["/parent/object", :get, true],
120
166
  ["/parent", :get, true],
121
167
  ["/parent/object/child", :get, true],
122
168
  ["/parent/sibling", :get, true],
123
169
  ["/uncle/cousin", :get, true],
124
170
  ["/uncle", :get, true],
171
+ # Do a PUT
125
172
  ["/parent/object", :put, true],
173
+ # Check that cache is cleared in the right places
126
174
  ["/parent/object", :get, true],
127
175
  ["/parent", :get, true],
128
176
  ["/parent/object/child", :get, true],
@@ -130,17 +178,22 @@ describe AMEE::Connection do
130
178
  ["/uncle/cousin", :get, false],
131
179
  ["/uncle", :get, false],
132
180
  ])
181
+ end
133
182
  end
134
183
 
135
184
  it "handles POST requests" do
185
+ VCR.use_cassette("AMEE_Connection_Caching/automatic_invalidation_for_post") do
136
186
  test_invalidation_sequence([
187
+ # Fill the cache
137
188
  ["/parent/object", :get, true],
138
189
  ["/parent", :get, true],
139
190
  ["/parent/object/child", :get, true],
140
191
  ["/parent/sibling", :get, true],
141
192
  ["/uncle/cousin", :get, true],
142
193
  ["/uncle", :get, true],
194
+ # Do a POST
143
195
  ["/parent/object", :post, true],
196
+ # Check that cache is cleared in the right places
144
197
  ["/parent/object", :get, true],
145
198
  ["/parent", :get, false],
146
199
  ["/parent/object/child", :get, true],
@@ -149,16 +202,21 @@ describe AMEE::Connection do
149
202
  ["/uncle", :get, false],
150
203
  ])
151
204
  end
205
+ end
152
206
 
153
207
  it "handles DELETE requests" do
208
+ VCR.use_cassette("AMEE_Connection_Caching/automatic_invalidation_for_delete") do
154
209
  test_invalidation_sequence([
210
+ # Fill the cache
155
211
  ["/parent/object", :get, true],
156
212
  ["/parent", :get, true],
157
213
  ["/parent/object/child", :get, true],
158
214
  ["/parent/sibling", :get, true],
159
215
  ["/uncle/cousin", :get, true],
160
216
  ["/uncle", :get, true],
217
+ # Do a DELETE
161
218
  ["/parent/object", :delete, true],
219
+ # Check that cache is cleared in the right places
162
220
  ["/parent/object", :get, true],
163
221
  ["/parent", :get, true],
164
222
  ["/parent/object/child", :get, true],
@@ -167,6 +225,7 @@ describe AMEE::Connection do
167
225
  ["/uncle", :get, false],
168
226
  ])
169
227
  end
228
+ end
170
229
 
171
230
  end
172
231