rets 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56d0e6818b0033682fe65525f1c73a2eb4ae42bb
4
- data.tar.gz: ee6fea4f6d3f028cd6247fb75896e5d70a6abe73
3
+ metadata.gz: 2ed1e7653c004159b6f9042dc917f9c8a30be926
4
+ data.tar.gz: 4b2275a656b7240b5d94a4d0f9c0ad5a62a18eeb
5
5
  SHA512:
6
- metadata.gz: 56b3c73c03e27236c1e99c13de713af4b45472e6853637bbfb77e90189c7e5a438ec13b8ad5cd0b9c2ada381f1d79bae72592cb3daab6067547a949f5fab51c0
7
- data.tar.gz: 6b67b8bd34c923e54b49232f161d4daf8662c8fd86899f87b02951d75d6eae9bb5bd19c7f42e0c2667ce41d8dbf949794cd5748762315a3feae8c40052e8afb9
6
+ metadata.gz: 3853c2929ab43105aba05ceebe185e4c3b86271bdc99b8ec42e9578921ee9d2740c98cfea2b31a13e22de6e3d5e4fa608f61399133593c43dae14f3c63ffc772
7
+ data.tar.gz: 7d177e112e61b54dc347811bdfa6a44aa2dd472ce45b061dbf180e636b4e5f2c4318e6d8ea08dc5c1013615118eefc4da401fa4b7bd3da629317d7022221f30b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ### 0.6.0 / 2013-10-30
2
+
3
+ * fix: fix spelling error that created misleading exceptions
4
+ * feature: track stats for http requests sent
5
+ * feature: raise an exception if the login action doesn't return an http 200 status
6
+ * feature: add better class description and more fields to print tree
7
+ * feature: support http proxies
8
+ * feature: customizable http timeouts
9
+ * feature: add logging http headers when in debug mode
10
+ * feature: strip invalid utf8 from responses before parsing
11
+ * fix: don't raise an exception on a 401 after logout
12
+ * fix: treat no matching records status without a count node as a zero count
13
+ * feature: add an option for loading custom ca_certs
14
+ * feature: remove invalid resource types from metadata
15
+ * feature: special case http 412
16
+ * feature: add max_retries option
17
+
18
+ ### 0.5.1 / 2013-10-30
19
+
20
+ * fix: 0.5.0 was broken, fix gem Manifest to fix gem
21
+
1
22
  ### 0.5.0 / 2013-09-05
2
23
 
3
24
  * feature: Allow client.count to get integer count
data/Manifest.txt CHANGED
@@ -25,3 +25,4 @@ test/test_locking_http_client.rb
25
25
  test/test_metadata.rb
26
26
  test/test_parser_compact.rb
27
27
  test/test_parser_multipart.rb
28
+ test/vcr_cassettes/unauthorized_response.yml
data/README.md CHANGED
@@ -17,6 +17,10 @@ A pure-ruby library for fetching data from [RETS] servers.
17
17
  [net-http-persistent]: http://seattlerb.rubyforge.org/net-http-persistent/
18
18
  [nokogiri]: http://nokogiri.org
19
19
 
20
+ ## EXAMPLE USAGE:
21
+
22
+ We need work in this area! There are currently a few guideline examples in the `example` folder on connecting, fetching a property's data, and fetching a property's photos.
23
+
20
24
  ## LICENSE:
21
25
 
22
26
  (The MIT License)
data/lib/rets.rb CHANGED
@@ -3,7 +3,7 @@ require 'digest/md5'
3
3
  require 'nokogiri'
4
4
 
5
5
  module Rets
6
- VERSION = '0.5.1'
6
+ VERSION = '0.6.0'
7
7
 
8
8
  MalformedResponse = Class.new(ArgumentError)
9
9
  UnknownResponse = Class.new(ArgumentError)
@@ -31,7 +31,7 @@ module Rets
31
31
  attr_reader :capability_name
32
32
  def initialize(capability_name)
33
33
  @capability_name = capability_name
34
- super("unknown capabilitiy #{capability_name}")
34
+ super("unknown capability #{capability_name}")
35
35
  end
36
36
  end
37
37
  end
data/lib/rets/client.rb CHANGED
@@ -31,7 +31,20 @@ module Rets
31
31
  self.logger = @options[:logger] || FakeLogger.new
32
32
  @client_progress = ClientProgressReporter.new(self.logger, options[:stats_collector], options[:stats_prefix])
33
33
  @cached_metadata = @options[:metadata]
34
- @http = HTTPClient.new
34
+ if @options[:http_proxy]
35
+ @http = HTTPClient.new(options.fetch(:http_proxy))
36
+
37
+ if @options[:proxy_username]
38
+ @http.set_proxy_auth(options.fetch(:proxy_username), options.fetch(:proxy_password))
39
+ end
40
+ else
41
+ @http = HTTPClient.new
42
+ end
43
+
44
+ if @options[:receive_timeout]
45
+ @http.receive_timeout = @options[:receive_timeout]
46
+ end
47
+
35
48
  @http.set_cookie_store(options[:cookie_store]) if options[:cookie_store]
36
49
 
37
50
  @http_client = Rets::HttpClient.new(@http, @options, @logger, @login_url)
@@ -61,6 +74,10 @@ module Rets
61
74
  raise NoLogout.new('No logout method found for rets client')
62
75
  end
63
76
  http_get(capability_url("Logout"))
77
+ rescue UnknownResponse => e
78
+ unless e.message.match(/expected a 200, but got 401/)
79
+ raise e
80
+ end
64
81
  end
65
82
 
66
83
  # Finds records.
@@ -98,7 +115,7 @@ module Rets
98
115
  begin
99
116
  find_every(opts, resolve)
100
117
  rescue AuthorizationFailure, InvalidRequest => e
101
- if retries < 3
118
+ if retries < opts.fetch(:max_retries, 3)
102
119
  retries += 1
103
120
  @client_progress.find_with_retries_failed_a_retry(e, retries)
104
121
  clean_setup
@@ -117,7 +134,7 @@ module Rets
117
134
  if opts[:count] == COUNT.only
118
135
  Parser::Compact.get_count(res.body)
119
136
  else
120
- results = Parser::Compact.parse_document(res.body)
137
+ results = Parser::Compact.parse_document(res.body.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace))
121
138
  if resolve
122
139
  rets_class = find_rets_class(opts[:search_type], opts[:class])
123
140
  decorate_results(results, rets_class)
@@ -239,11 +256,18 @@ module Rets
239
256
  self.metadata = @cached_metadata
240
257
  else
241
258
  @client_progress.bad_cached_metadata(@cached_metadata)
242
- metadata_fetcher = lambda { |type| retrieve_metadata_type(type) }
243
- self.metadata = Metadata::Root.new(&metadata_fetcher)
259
+ self.metadata = Metadata::Root.new(logger, retrieve_metadata)
244
260
  end
245
261
  end
246
262
 
263
+ def retrieve_metadata
264
+ raw_metadata = {}
265
+ Metadata::METADATA_TYPES.each {|type|
266
+ raw_metadata[type] = retrieve_metadata_type(type)
267
+ }
268
+ raw_metadata
269
+ end
270
+
247
271
  def retrieve_metadata_type(type)
248
272
  res = http_post(capability_url("GetMetadata"),
249
273
  { "Format" => "COMPACT",
@@ -322,6 +346,13 @@ module Rets
322
346
 
323
347
  class ErrorChecker
324
348
  def self.check(response)
349
+ # some RETS servers returns HTTP code 412 when session cookie expired, yet the response body
350
+ # passes XML check. We need to special case for this situation.
351
+ # This method is also called from multipart.rb where there are headers and body but no status_code
352
+ if response.respond_to?(:status_code) && response.status_code == 412
353
+ raise HttpError, "HTTP status: #{response.status_code}, body: #{response.body}"
354
+ end
355
+
325
356
  # some RETS servers return success code in XML body but failure code 4xx in http status
326
357
  # If xml body is present we ignore http status
327
358
 
@@ -7,13 +7,16 @@ module Rets
7
7
  @options = options
8
8
  @logger = logger
9
9
  @login_url = login_url
10
+ @options.fetch(:ca_certs, []).each {|c| @http.ssl_config.add_trust_ca(c) }
10
11
  end
11
12
 
12
13
  def http_get(url, params=nil, extra_headers={})
13
14
  http.set_auth(url, options[:username], options[:password])
14
15
  headers = extra_headers.merge(rets_extra_headers)
15
- res = http.get(url, params, headers)
16
- log_http_traffic("POST", url, params, headers, res)
16
+ res = nil
17
+ log_http_traffic("POST", url, params, headers) do
18
+ res = http.get(url, params, headers)
19
+ end
17
20
  Client::ErrorChecker.check(res)
18
21
  res
19
22
  end
@@ -21,12 +24,34 @@ module Rets
21
24
  def http_post(url, params, extra_headers = {})
22
25
  http.set_auth(url, options[:username], options[:password])
23
26
  headers = extra_headers.merge(rets_extra_headers)
24
- res = http.post(url, params, headers)
25
- log_http_traffic("POST", url, params, headers, res)
27
+ res = nil
28
+ log_http_traffic("POST", url, params, headers) do
29
+ res = http.post(url, params, headers)
30
+ end
26
31
  Client::ErrorChecker.check(res)
27
32
  res
28
33
  end
29
34
 
35
+ def log_http_traffic(method, url, params, headers, &block)
36
+ # optimization, we don't want to compute log params
37
+ # if logging is off
38
+ if logger.debug?
39
+ logger.debug "Rets::Client >> #{method} #{url}"
40
+ logger.debug "Rets::Client >> params = #{params.inspect}"
41
+ logger.debug "Rets::Client >> headers = #{headers.inspect}"
42
+ end
43
+
44
+ res = block.call
45
+
46
+ # optimization, we don't want to compute log params
47
+ # if logging is off, especially when there is a loop just
48
+ # for logging
49
+ if logger.debug?
50
+ logger.debug "Rets::Client << Status #{res.status_code}"
51
+ res.headers.each { |k, v| logger.debug "Rets::Client << #{k}: #{v}" }
52
+ end
53
+ end
54
+
30
55
  def save_cookie_store(force=nil)
31
56
  if options[:cookie_store]
32
57
  if force
@@ -37,15 +62,6 @@ module Rets
37
62
  end
38
63
  end
39
64
 
40
- def log_http_traffic(method, url, params, headers, res)
41
- return unless logger.debug?
42
- logger.debug "Rets::Client >> #{method} #{url}"
43
- logger.debug "Rets::Client >> params = #{params.inspect}"
44
- logger.debug "Rets::Client >> headers = #{headers.inspect}"
45
- logger.debug "Rets::Client << Status #{res.status_code}"
46
- res.headers.each { |k, v| logger.debug "Rets::Client << #{k}: #{v}" }
47
- end
48
-
49
65
  def rets_extra_headers
50
66
  user_agent = options[:agent] || "Client/1.0"
51
67
  rets_version = options[:version] || "RETS/1.7.2"
@@ -10,7 +10,7 @@ module Rets
10
10
  end
11
11
 
12
12
  def print_tree
13
- puts " #{long_value} -> #{value}"
13
+ puts " #{long_value} -> #{value}"
14
14
  end
15
15
  end
16
16
  end
@@ -1,6 +1,7 @@
1
1
  module Rets
2
2
  module Metadata
3
3
  class Resource
4
+ class MissingRetsClass < RuntimeError; end
4
5
  attr_accessor :rets_classes
5
6
  attr_accessor :lookup_types
6
7
  attr_accessor :key_field
@@ -24,7 +25,12 @@ module Rets
24
25
  end
25
26
 
26
27
  def self.find_rets_classes(metadata, resource)
27
- metadata[:class].detect { |c| c.resource == resource.id }.classes
28
+ class_container = metadata[:class].detect { |c| c.resource == resource.id }
29
+ if class_container.nil?
30
+ raise MissingRetsClass.new("No Metadata classes for #{resource.id}")
31
+ else
32
+ class_container.classes
33
+ end
28
34
  end
29
35
 
30
36
  def self.build_lookup_tree(resource, metadata)
@@ -52,12 +58,15 @@ module Rets
52
58
  end
53
59
  end
54
60
 
55
- def self.build(resource_fragment, metadata)
61
+ def self.build(resource_fragment, metadata, logger)
56
62
  resource = new(resource_fragment)
57
63
 
58
64
  resource.lookup_types = build_lookup_tree(resource, metadata)
59
65
  resource.rets_classes = build_classes(resource, metadata)
60
66
  resource
67
+ rescue MissingRetsClass => e
68
+ logger.warn(e.message)
69
+ nil
61
70
  end
62
71
 
63
72
  def print_tree
@@ -3,12 +3,16 @@ module Rets
3
3
  class RetsClass
4
4
  attr_accessor :tables
5
5
  attr_accessor :name
6
+ attr_accessor :visible_name
7
+ attr_accessor :description
6
8
  attr_accessor :resource
7
9
 
8
10
  def initialize(rets_class_fragment, resource)
9
11
  self.resource = resource
10
12
  self.tables = []
11
13
  self.name = rets_class_fragment["ClassName"]
14
+ self.visible_name = rets_class_fragment["VisibleName"]
15
+ self.description = rets_class_fragment["Description"]
12
16
  end
13
17
 
14
18
  def self.find_table_container(metadata, resource, rets_class)
@@ -31,6 +35,8 @@ module Rets
31
35
 
32
36
  def print_tree
33
37
  puts " Class: #{name}"
38
+ puts " Visible Name: #{visible_name}"
39
+ puts " Description : #{description}"
34
40
  tables.each(&:print_tree)
35
41
  end
36
42
 
@@ -47,33 +47,25 @@ module Rets
47
47
  # Sources are the raw xml documents fetched for each metadata type
48
48
  # they are stored as a hash with the type names as their keys
49
49
  # and the raw xml as the values
50
- attr_accessor :sources
50
+ attr_reader :sources
51
+
52
+ # Metadata can be unmarshalled from cache. @logger is not set during that process, constructor is not called.
53
+ # Client code must set it after unmarshalling.
54
+ attr_reader :logger
51
55
 
52
56
  # fetcher is a proc that inverts control to the client to retrieve metadata
53
57
  # types
54
- def initialize(&fetcher)
58
+ def initialize(logger, sources)
59
+ @logger = logger
55
60
  @tree = nil
56
61
  @metadata_types = nil # TODO think up a better name ... containers?
57
- @sources = {}
58
-
59
- # allow Root's to be built with no fetcher. Makes for easy testing
60
- return unless block_given?
61
-
62
- fetch_sources(&fetcher)
63
- end
64
-
65
- def fetch_sources(&fetcher)
66
- self.sources = Hash[*METADATA_TYPES.map {|type| [type, fetcher.call(type)] }.flatten]
62
+ @sources = sources
67
63
  end
68
64
 
69
65
  def marshal_dump
70
66
  sources
71
67
  end
72
68
 
73
- def marshal_load(sources)
74
- self.sources = sources
75
- end
76
-
77
69
  def version
78
70
  metadata_types[:system].first.version
79
71
  end
@@ -87,11 +79,11 @@ module Rets
87
79
  # version was published, or a version number. These values may or may
88
80
  # not exist on any given rets server.
89
81
  def current?(current_timestamp, current_version)
90
- if !current_version.to_s.empty? && !version.to_s.empty?
91
- current_version == version
92
- else
82
+ if !current_version.to_s.empty? && !version.to_s.empty?
83
+ current_version == version
84
+ else
93
85
  current_timestamp ? current_timestamp == date : true
94
- end
86
+ end
95
87
  end
96
88
 
97
89
  def build_tree
@@ -101,8 +93,9 @@ module Rets
101
93
 
102
94
  resource_containers.each do |resource_container|
103
95
  resource_container.rows.each do |resource_fragment|
104
- resource = Resource.build(resource_fragment, metadata_types)
105
- tree[resource.id.downcase] = resource
96
+ resource = Resource.build(resource_fragment, metadata_types, @logger)
97
+ #some mlses list resource types without an associated data, throw those away
98
+ tree[resource.id.downcase] = resource if resource
106
99
  end
107
100
  end
108
101
 
@@ -33,6 +33,7 @@ module Rets
33
33
  puts " StandardName: #{ table_fragment["StandardName"] }"
34
34
  puts " Units: #{ table_fragment["Units"] }"
35
35
  puts " Searchable: #{ table_fragment["Searchable"] }"
36
+ puts " Required: #{table_fragment['Required']}"
36
37
  end
37
38
 
38
39
  def resolve(value)
@@ -67,6 +68,13 @@ module Rets
67
68
 
68
69
  def print_tree
69
70
  puts " LookupTable: #{name}"
71
+ puts " Required: #{table_fragment['Required']}"
72
+ puts " Searchable: #{ table_fragment["Searchable"] }"
73
+ puts " Units: #{ table_fragment["Units"] }"
74
+ puts " ShortName: #{ table_fragment["ShortName"] }"
75
+ puts " LongName: #{ table_fragment["LongName"] }"
76
+ puts " StandardName: #{ table_fragment["StandardName"] }"
77
+ puts " Types:"
70
78
 
71
79
  lookup_types.each(&:print_tree)
72
80
  end
@@ -49,8 +49,14 @@ module Rets
49
49
 
50
50
  def self.get_count(xml)
51
51
  doc = Nokogiri.parse(xml.to_s)
52
- doc.at("//COUNT").attr('Records').to_i
52
+ if node = doc.at("//COUNT")
53
+ return node.attr('Records').to_i
54
+ elsif node = doc.at("//RETS-STATUS")
55
+ # Handle <RETS-STATUS ReplyCode="20201" ReplyText="No matching records were found" />
56
+ return 0 if node.attr('ReplyCode') == '20201'
57
+ end
53
58
  end
59
+
54
60
  end
55
61
  end
56
62
  end
data/test/fixtures.rb CHANGED
@@ -27,6 +27,13 @@ COUNT_ONLY = <<XML
27
27
  </RETS>
28
28
  XML
29
29
 
30
+ RETS_STATUS_NO_MATCHING_RECORDS = <<XML
31
+ <?xml version="1.0"?>
32
+ <RETS ReplyCode="0" ReplyText="Operation Successful">
33
+ <RETS-STATUS ReplyCode="20201" ReplyText="No matching records were found" />
34
+ </RETS>
35
+ XML
36
+
30
37
  CAPABILITIES_WITH_WHITESPACE = <<XML
31
38
  <RETS ReplyCode="0" ReplyText="Operation Successful">
32
39
  <RETS-RESPONSE>
data/test/test_client.rb CHANGED
@@ -37,21 +37,23 @@ class TestClient < MiniTest::Test
37
37
  end
38
38
 
39
39
  def test_metadata_when_not_initialized_with_metadata
40
+ new_raw_metadata = stub(:new_raw_metadata)
41
+
40
42
  client = Rets::Client.new(:login_url => "http://example.com")
41
- Rets::Metadata::Root.expects(:new)
42
- client.metadata
43
+ client.stubs(:retrieve_metadata).returns(new_raw_metadata)
44
+
45
+ assert_same new_raw_metadata, client.metadata.marshal_dump
43
46
  end
44
47
 
45
- def test_initialize_with_old_metadata_cached_gets_new_metadata
48
+ def test_initialize_with_old_metadata_cached_contstructs_new_metadata_from_request
46
49
  metadata = stub(:current? => false)
47
- new_metadata = stub(:current? => false)
50
+ new_raw_metadata = stub(:new_raw_metadata)
51
+
48
52
  client = Rets::Client.new(:login_url => "http://example.com", :metadata => metadata)
49
- client.stubs(:capabilities => {})
50
- Rets::Metadata::Root.expects(:new => new_metadata).once
53
+ client.stubs(:capabilities).returns({})
54
+ client.stubs(:retrieve_metadata).returns(new_raw_metadata)
51
55
 
52
- assert_same new_metadata, client.metadata
53
- # This second call ensures the expectations on Root are met
54
- client.metadata
56
+ assert_same new_raw_metadata, client.metadata.marshal_dump
55
57
  end
56
58
 
57
59
  def test_initialize_with_current_metadata_cached_return_cached_metadata
@@ -210,4 +212,27 @@ class TestClient < MiniTest::Test
210
212
  assert_equal response, result
211
213
  end
212
214
 
215
+ def test_clean_setup_with_receive_timeout
216
+ HTTPClient.any_instance.expects(:receive_timeout=).with(1234)
217
+ @client = Rets::Client.new(
218
+ login_url: 'http://example.com/login',
219
+ receive_timeout: 1234
220
+ )
221
+ end
222
+
223
+ def test_clean_setup_with_proxy_auth
224
+ @login_url = 'http://example.com/login'
225
+ @proxy_url = 'http://example.com/proxy'
226
+ @proxy_username = 'username'
227
+ @proxy_password = 'password'
228
+ HTTPClient.any_instance.expects(:set_proxy_auth).with(@proxy_username, @proxy_password)
229
+
230
+ @client = Rets::Client.new(
231
+ login_url: @login_url,
232
+ http_proxy: @proxy_url,
233
+ proxy_username: @proxy_username,
234
+ proxy_password: @proxy_password
235
+ )
236
+ end
237
+
213
238
  end
@@ -1,8 +1,9 @@
1
1
  require_relative "helper"
2
+ require 'logger'
2
3
 
3
4
  class TestMetadata < MiniTest::Test
4
5
  def setup
5
- @root = Rets::Metadata::Root.new
6
+ @root = Rets::Metadata::Root.new(Logger.new(STDOUT), {})
6
7
  $VERBOSE = true
7
8
  end
8
9
 
@@ -10,23 +11,6 @@ class TestMetadata < MiniTest::Test
10
11
  $VERBOSE = false
11
12
  end
12
13
 
13
- def test_metadata_root_fetch_sources_returns_hash_of_metadata_types
14
- types = []
15
- fake_fetcher = lambda do |type|
16
- types << type
17
- end
18
-
19
- @root.fetch_sources(&fake_fetcher)
20
-
21
- assert_equal(Rets::Metadata::METADATA_TYPES, types)
22
- end
23
-
24
- def test_metadata_root_intialized_with_block
25
- external = false
26
- Rets::Metadata::Root.new { |source| external = true }
27
- assert external
28
- end
29
-
30
14
  def test_metadata_root_build_tree
31
15
  resource = stub(:id => "X")
32
16
  Rets::Metadata::Resource.stubs(:build => resource)
@@ -111,10 +95,11 @@ class TestMetadata < MiniTest::Test
111
95
 
112
96
  def test_metadata_root_metadata_types_constructs_a_hash_of_metadata_types_from_sources
113
97
  test_sources = { "X" => "Y", "Z" => "W" }
114
- @root.stubs(:sources => test_sources, :build_containers => "Y--")
115
- @root.metadata_types = nil
98
+ root = Rets::Metadata::Root.new(stub(:logger), test_sources)
99
+ root.stubs(:build_containers => "Y--")
116
100
  Nokogiri.stubs(:parse => "Y-")
117
- assert_equal({:x => "Y--", :z => "Y--"}, @root.metadata_types)
101
+
102
+ assert_equal({:x => "Y--", :z => "Y--"}, root.metadata_types)
118
103
  end
119
104
 
120
105
  def test_metadata_root_build_containers_selects_correct_tags
@@ -211,12 +196,30 @@ class TestMetadata < MiniTest::Test
211
196
  Rets::Metadata::Resource.stubs(:build_lookup_tree => lookup_types)
212
197
  Rets::Metadata::Resource.stubs(:build_classes => classes)
213
198
 
214
- resource = Rets::Metadata::Resource.build(fragment, metadata)
199
+ resource = Rets::Metadata::Resource.build(fragment, metadata, Logger.new(STDOUT))
215
200
 
216
201
  assert_equal(lookup_types, resource.lookup_types)
217
202
  assert_equal(classes, resource.rets_classes)
218
203
  end
219
204
 
205
+ def test_resource_build_with_incomplete_classes
206
+ fragment = { "ResourceID" => "test" }
207
+
208
+ lookup_types = stub(:lookup_types)
209
+ metadata = stub(:metadata)
210
+
211
+ Rets::Metadata::Resource.stubs(:build_lookup_tree => lookup_types)
212
+ Rets::Metadata::Resource.stubs(:build_classes).raises(Rets::Metadata::Resource::MissingRetsClass)
213
+
214
+ error_log = StringIO.new
215
+ resource = Rets::Metadata::Resource.build(fragment, metadata, Logger.new(error_log))
216
+
217
+ error_log.rewind
218
+ error_msg = error_log.read
219
+ assert error_msg.include?('MissingRetsClass')
220
+ assert_equal(nil, resource)
221
+ end
222
+
220
223
  def test_resource_find_lookup_containers
221
224
  resource = stub(:id => "id")
222
225
  metadata = { :lookup => [stub(:resource => "id"), stub(:resource => "id"), stub(:resource => "a")] }
@@ -440,18 +443,17 @@ class TestMetadata < MiniTest::Test
440
443
 
441
444
  def test_root_can_be_serialized
442
445
  sources = { :x => "a" }
443
-
444
- @root.sources = sources
445
-
446
- assert_equal sources, @root.marshal_dump
446
+ root = Rets::Metadata::Root.new(stub(:logger), sources)
447
+ assert_equal sources, root.marshal_dump
447
448
  end
448
449
 
449
450
  def test_root_can_be_unserialized
451
+ logger = stub(:logger)
450
452
  sources = { :x => "a" }
451
453
 
452
- @root.marshal_load(sources)
454
+ root_to_serialize = Rets::Metadata::Root.new(logger, sources)
455
+ new_root = Rets::Metadata::Root.new(logger, root_to_serialize.marshal_dump)
453
456
 
454
- assert_equal sources, @root.sources
457
+ assert_equal root_to_serialize.marshal_dump, new_root.marshal_dump
455
458
  end
456
-
457
459
  end
@@ -66,6 +66,11 @@ class TestParserCompact < MiniTest::Test
66
66
  assert_equal 1234, count
67
67
  end
68
68
 
69
+ def test_get_count_with_no_matching_records
70
+ count = Rets::Parser::Compact.get_count(RETS_STATUS_NO_MATCHING_RECORDS)
71
+ assert_equal 0, count
72
+ end
73
+
69
74
  def test_parse_example
70
75
  rows = Rets::Parser::Compact.parse_document(Nokogiri.parse(SAMPLE_COMPACT))
71
76
 
@@ -0,0 +1,262 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://rets.example.com/GetObject.asmx/GetObject
6
+ body:
7
+ encoding: US-ASCII
8
+ string: Resource=Property&Type=Photo&ID=2661580%3A1&Location=0
9
+ headers:
10
+ User-Agent:
11
+ - Estately/1.0
12
+ Host:
13
+ - rets.example.com:80
14
+ Rets-Version:
15
+ - RETS/1.7.2
16
+ Authorization:
17
+ - Digest username="login", realm="fake_realm", qop="auth",
18
+ uri="/Login.asmx/Login", nonce="a8f4bc805062602c8ba7a87b2109f808", nc=00000001,
19
+ cnonce="6e2dd038eea6cbacf0b956fd11f914b6", response="fake_digest",
20
+ opaque="96eda461-41d0-4624-b7fe-b59e966035f3"
21
+ Cookie:
22
+ - ASP.NET_SessionId=mapij045k3bphj3gqqgpmmrx
23
+ Accept:
24
+ - image/jpeg, image/png;q=0.5, image/gif;q=0.1
25
+ Content-Type:
26
+ - application/x-www-form-urlencoded
27
+ Content-Length:
28
+ - '54'
29
+ response:
30
+ status:
31
+ code: 401
32
+ message: Unauthorized
33
+ headers:
34
+ Connection:
35
+ - close
36
+ Date:
37
+ - Thu, 26 Jul 2012 00:11:02 GMT
38
+ Server:
39
+ - Microsoft-IIS/6.0
40
+ Cache-Control:
41
+ - private
42
+ - private
43
+ X-Aspnet-Version:
44
+ - 2.0.50727
45
+ Www-Authenticate:
46
+ - Digest realm="fake_realm",nonce="dfd74deac03147e1a4fff598679f1a80",opaque="8e5029c6-bb9c-4562-b440-e7c2b8960743",qop="auth"
47
+ Content-Type:
48
+ - text/plain
49
+ body:
50
+ encoding: US-ASCII
51
+ string: ! "Unauthorized Request. Digest Session Invalid Reauthenticate\r\nReference:
52
+ e0ff89c0-2d64-4287-a6b7-6be27c7d3ade"
53
+ http_version:
54
+ recorded_at: Thu, 26 Jul 2012 00:11:13 GMT
55
+ - request:
56
+ method: post
57
+ uri: http://rets.example.com/Login.asmx/Login
58
+ body:
59
+ encoding: US-ASCII
60
+ string: ''
61
+ headers:
62
+ User-Agent:
63
+ - Estately/1.0
64
+ Host:
65
+ - rets.example.com:80
66
+ Rets-Version:
67
+ - RETS/1.7.2
68
+ Accept:
69
+ - ! '*/*'
70
+ response:
71
+ status:
72
+ code: 401
73
+ message: Unauthorized
74
+ headers:
75
+ Connection:
76
+ - close
77
+ Date:
78
+ - Thu, 26 Jul 2012 00:11:03 GMT
79
+ Server:
80
+ - Microsoft-IIS/6.0
81
+ Cache-Control:
82
+ - private
83
+ - private
84
+ X-Aspnet-Version:
85
+ - 2.0.50727
86
+ Www-Authenticate:
87
+ - Digest realm="fake_realm",nonce="17fdfe2c6125296bd93bb81bd88dc4be",opaque="aee1e7da-60eb-41b3-b704-8eb3969645fd",qop="auth"
88
+ Content-Type:
89
+ - text/plain
90
+ body:
91
+ encoding: US-ASCII
92
+ string: ! "Unauthorized Request. Authentication needed\r\nReference: c2c5ab9f-8c12-42bb-94ce-5b6ebf156da4"
93
+ http_version:
94
+ recorded_at: Thu, 26 Jul 2012 00:11:13 GMT
95
+ - request:
96
+ method: post
97
+ uri: http://rets.example.com/Login.asmx/Login
98
+ body:
99
+ encoding: US-ASCII
100
+ string: ''
101
+ headers:
102
+ User-Agent:
103
+ - Estately/1.0
104
+ Host:
105
+ - rets.example.com:80
106
+ Rets-Version:
107
+ - RETS/1.7.2
108
+ Authorization:
109
+ - Digest username="login", realm="fake_realm", qop="auth",
110
+ uri="/Login.asmx/Login", nonce="17fdfe2c6125296bd93bb81bd88dc4be", nc=00000001,
111
+ cnonce="ad805b9e2d6f08ad82175085e5febd8d", response="7402076f53663602f052e1aa343d3dab",
112
+ opaque="aee1e7da-60eb-41b3-b704-8eb3969645fd"
113
+ Accept:
114
+ - ! '*/*'
115
+ response:
116
+ status:
117
+ code: 200
118
+ message: OK
119
+ headers:
120
+ Date:
121
+ - Thu, 26 Jul 2012 00:11:03 GMT
122
+ Server:
123
+ - Microsoft-IIS/6.0
124
+ Cache-Control:
125
+ - private
126
+ - private, max-age=0
127
+ X-Aspnet-Version:
128
+ - 2.0.50727
129
+ Rets-Version:
130
+ - RETS/1.7.2
131
+ Rets-Server:
132
+ - Interealty-RETS/1.5.247.0
133
+ Transfer-Encoding:
134
+ - chunked
135
+ Set-Cookie:
136
+ - ASP.NET_SessionId=vuesmq55l0cpjy45zyn0e555; path=/; HttpOnly
137
+ - RETS-Session-ID=vuesmq55l0cpjy45zyn0e555; path=/
138
+ Content-Type:
139
+ - text/xml
140
+ body:
141
+ encoding: US-ASCII
142
+ string: ! "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<RETS ReplyCode=\"0\"
143
+ ReplyText=\"Success. Reference ID: a3073726-a011-4391-b22e-900adae5184b\">\r\n
144
+ \ <RETS-RESPONSE>\r\nMemberName=John Wolff\r\nUser=23756,60,RH,login\r\nBroker=BRDA,BRDA01\r\nMetadataVersion=31.76.69587\r\nMetadataTimeStamp=2012-07-24T17:33:07Z\r\nMinMetadataTimeStamp=2012-07-24T17:33:07Z\r\nTimeoutSeconds=7200\r\nChangePassword=/ChangePassword.asmx/ChangePassword\r\nGetObject=/GetObject.asmx/GetObject\r\nLogin=/Login.asmx/Login\r\nLogout=/Logout.asmx/Logout\r\nSearch=/Search.asmx/Search\r\nGetMetadata=/GetMetadata.asmx/GetMetadata\r\n</RETS-RESPONSE>\r\n</RETS>"
145
+ http_version:
146
+ recorded_at: Thu, 26 Jul 2012 00:11:14 GMT
147
+ - request:
148
+ method: post
149
+ uri: http://rets.example.com/Login.asmx/Login
150
+ body:
151
+ encoding: US-ASCII
152
+ string: ''
153
+ headers:
154
+ User-Agent:
155
+ - Estately/1.0
156
+ Host:
157
+ - rets.example.com:80
158
+ Rets-Version:
159
+ - RETS/1.7.2
160
+ Authorization:
161
+ - Digest username="login", realm="fake_realm", qop="auth",
162
+ uri="/Login.asmx/Login", nonce="17fdfe2c6125296bd93bb81bd88dc4be", nc=00000001,
163
+ cnonce="ad805b9e2d6f08ad82175085e5febd8d", response="7402076f53663602f052e1aa343d3dab",
164
+ opaque="aee1e7da-60eb-41b3-b704-8eb3969645fd"
165
+ Cookie:
166
+ - ASP.NET_SessionId=vuesmq55l0cpjy45zyn0e555; RETS-Session-ID=vuesmq55l0cpjy45zyn0e555
167
+ Accept:
168
+ - ! '*/*'
169
+ response:
170
+ status:
171
+ code: 200
172
+ message: OK
173
+ headers:
174
+ Date:
175
+ - Thu, 26 Jul 2012 00:11:03 GMT
176
+ Server:
177
+ - Microsoft-IIS/6.0
178
+ Cache-Control:
179
+ - private
180
+ - private, max-age=0
181
+ X-Aspnet-Version:
182
+ - 2.0.50727
183
+ Rets-Version:
184
+ - RETS/1.7.2
185
+ Rets-Server:
186
+ - Interealty-RETS/1.5.247.0
187
+ Transfer-Encoding:
188
+ - chunked
189
+ Set-Cookie:
190
+ - RETS-Session-ID=vuesmq55l0cpjy45zyn0e555; path=/
191
+ Content-Type:
192
+ - text/xml
193
+ body:
194
+ encoding: US-ASCII
195
+ string: ! "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<RETS ReplyCode=\"0\"
196
+ ReplyText=\"Success. Reference ID: a48b4f59-cb22-41be-8e7b-1cee28fb8644\">\r\n
197
+ \ <RETS-RESPONSE>\r\nMemberName=John Wolff\r\nUser=23756,60,RH,login\r\nBroker=BRDA,BRDA01\r\nMetadataVersion=31.76.69587\r\nMetadataTimeStamp=2012-07-24T17:33:07Z\r\nMinMetadataTimeStamp=2012-07-24T17:33:07Z\r\nTimeoutSeconds=7200\r\nChangePassword=/ChangePassword.asmx/ChangePassword\r\nGetObject=/GetObject.asmx/GetObject\r\nLogin=/Login.asmx/Login\r\nLogout=/Logout.asmx/Logout\r\nSearch=/Search.asmx/Search\r\nGetMetadata=/GetMetadata.asmx/GetMetadata\r\n</RETS-RESPONSE>\r\n</RETS>"
198
+ http_version:
199
+ recorded_at: Thu, 26 Jul 2012 00:11:14 GMT
200
+ - request:
201
+ method: post
202
+ uri: http://rets.example.com/GetObject.asmx/GetObject
203
+ body:
204
+ encoding: US-ASCII
205
+ string: Resource=Property&Type=Photo&ID=2661580%3A1&Location=0
206
+ headers:
207
+ User-Agent:
208
+ - Estately/1.0
209
+ Host:
210
+ - rets.example.com:80
211
+ Rets-Version:
212
+ - RETS/1.7.2
213
+ Authorization:
214
+ - Digest username="login", realm="fake_realm", qop="auth",
215
+ uri="/Login.asmx/Login", nonce="17fdfe2c6125296bd93bb81bd88dc4be", nc=00000001,
216
+ cnonce="ad805b9e2d6f08ad82175085e5febd8d", response="7402076f53663602f052e1aa343d3dab",
217
+ opaque="aee1e7da-60eb-41b3-b704-8eb3969645fd"
218
+ Cookie:
219
+ - ASP.NET_SessionId=vuesmq55l0cpjy45zyn0e555; RETS-Session-ID=vuesmq55l0cpjy45zyn0e555
220
+ Accept:
221
+ - image/jpeg, image/png;q=0.5, image/gif;q=0.1
222
+ Content-Type:
223
+ - application/x-www-form-urlencoded
224
+ Content-Length:
225
+ - '54'
226
+ response:
227
+ status:
228
+ code: 200
229
+ message: OK
230
+ headers:
231
+ Date:
232
+ - Thu, 26 Jul 2012 00:11:04 GMT
233
+ Server:
234
+ - Microsoft-IIS/6.0
235
+ Cache-Control:
236
+ - private
237
+ - private, max-age=0
238
+ X-Aspnet-Version:
239
+ - 2.0.50727
240
+ Mime-Version:
241
+ - '1.0'
242
+ Rets-Version:
243
+ - RETS/1.7.2
244
+ Rets-Server:
245
+ - Interealty-RETS/1.5.247.0
246
+ Content-Id:
247
+ - '2661580'
248
+ Object-Id:
249
+ - '1'
250
+ Transfer-Encoding:
251
+ - chunked
252
+ Set-Cookie:
253
+ - RETS-Session-ID=vuesmq55l0cpjy45zyn0e555; path=/
254
+ Content-Type:
255
+ - image/jpeg
256
+ body:
257
+ encoding: ASCII-8BIT
258
+ string: !binary |-
259
+ /9j/4AAQSkZJRgABAQEAYABgAAD///2Q==
260
+ http_version:
261
+ recorded_at: Thu, 26 Jul 2012 00:11:14 GMT
262
+ recorded_with: VCR 2.2.2
metadata CHANGED
@@ -1,119 +1,118 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Estately, Inc. Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-30 00:00:00.000000000 Z
11
+ date: 2014-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: 2.3.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.3.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.5.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.5.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rdoc
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '4.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '4.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mocha
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 0.11.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.11.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: vcr
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: 2.2.2
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 2.2.2
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: webmock
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ~>
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: 1.8.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ~>
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.8.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: hoe
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ~>
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '3.7'
103
+ version: '3.6'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ~>
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '3.7'
111
- description: ! '[![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
112
-
110
+ version: '3.6'
111
+ description: |-
112
+ [![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
113
113
  A pure-ruby library for fetching data from [RETS] servers.
114
114
 
115
-
116
- [RETS]: http://www.rets.org'
115
+ [RETS]: http://www.rets.org
117
116
  email:
118
117
  - opensource@estately.com
119
118
  executables:
@@ -124,6 +123,7 @@ extra_rdoc_files:
124
123
  - Manifest.txt
125
124
  - README.md
126
125
  files:
126
+ - ".gemtest"
127
127
  - CHANGELOG.md
128
128
  - Manifest.txt
129
129
  - README.md
@@ -151,34 +151,33 @@ files:
151
151
  - test/test_metadata.rb
152
152
  - test/test_parser_compact.rb
153
153
  - test/test_parser_multipart.rb
154
- - .gemtest
154
+ - test/vcr_cassettes/unauthorized_response.yml
155
155
  homepage: http://github.com/estately/rets
156
- licenses:
157
- - MIT
156
+ licenses: []
158
157
  metadata: {}
159
158
  post_install_message:
160
159
  rdoc_options:
161
- - --main
160
+ - "--main"
162
161
  - README.md
163
162
  require_paths:
164
163
  - lib
165
164
  required_ruby_version: !ruby/object:Gem::Requirement
166
165
  requirements:
167
- - - ! '>='
166
+ - - ">="
168
167
  - !ruby/object:Gem::Version
169
168
  version: '0'
170
169
  required_rubygems_version: !ruby/object:Gem::Requirement
171
170
  requirements:
172
- - - ! '>='
171
+ - - ">="
173
172
  - !ruby/object:Gem::Version
174
173
  version: '0'
175
174
  requirements: []
176
175
  rubyforge_project: rets
177
- rubygems_version: 2.1.10
176
+ rubygems_version: 2.2.0
178
177
  signing_key:
179
178
  specification_version: 4
180
- summary: ! '[![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
181
- A pure-ruby library for fetching data from [RETS] servers'
179
+ summary: "[![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](http://travis-ci.org/estately/rets)
180
+ A pure-ruby library for fetching data from [RETS] servers"
182
181
  test_files:
183
182
  - test/test_client.rb
184
183
  - test/test_locking_http_client.rb