amee 4.3.2 → 4.4.0

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