rets 0.9.0 → 0.10.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Manifest.txt +24 -0
  4. data/README.md +73 -1
  5. data/Rakefile +1 -1
  6. data/lib/rets.rb +202 -1
  7. data/lib/rets/client.rb +83 -94
  8. data/lib/rets/http_client.rb +42 -0
  9. data/lib/rets/metadata.rb +15 -3
  10. data/lib/rets/metadata/caching.rb +59 -0
  11. data/lib/rets/metadata/file_cache.rb +29 -0
  12. data/lib/rets/metadata/json_serializer.rb +27 -0
  13. data/lib/rets/metadata/lookup_table.rb +65 -0
  14. data/lib/rets/metadata/lookup_type.rb +3 -4
  15. data/lib/rets/metadata/marshal_serializer.rb +27 -0
  16. data/lib/rets/metadata/multi_lookup_table.rb +70 -0
  17. data/lib/rets/metadata/null_cache.rb +24 -0
  18. data/lib/rets/metadata/resource.rb +39 -29
  19. data/lib/rets/metadata/rets_class.rb +27 -23
  20. data/lib/rets/metadata/rets_object.rb +32 -0
  21. data/lib/rets/metadata/table.rb +9 -101
  22. data/lib/rets/metadata/table_factory.rb +19 -0
  23. data/lib/rets/metadata/yaml_serializer.rb +27 -0
  24. data/lib/rets/parser/compact.rb +61 -18
  25. data/lib/rets/parser/error_checker.rb +8 -1
  26. data/test/fixtures.rb +58 -0
  27. data/test/test_caching.rb +89 -0
  28. data/test/test_client.rb +44 -24
  29. data/test/test_error_checker.rb +18 -0
  30. data/test/test_file_cache.rb +42 -0
  31. data/test/test_http_client.rb +96 -60
  32. data/test/test_json_serializer.rb +26 -0
  33. data/test/test_marshal_serializer.rb +26 -0
  34. data/test/test_metadata.rb +62 -450
  35. data/test/test_metadata_class.rb +50 -0
  36. data/test/test_metadata_lookup_table.rb +21 -0
  37. data/test/test_metadata_lookup_type.rb +12 -0
  38. data/test/test_metadata_multi_lookup_table.rb +60 -0
  39. data/test/test_metadata_object.rb +20 -0
  40. data/test/test_metadata_resource.rb +140 -0
  41. data/test/test_metadata_root.rb +151 -0
  42. data/test/test_metadata_table.rb +21 -0
  43. data/test/test_metadata_table_factory.rb +24 -0
  44. data/test/test_parser_compact.rb +23 -28
  45. data/test/test_yaml_serializer.rb +26 -0
  46. metadata +29 -5
@@ -0,0 +1,89 @@
1
+ require_relative "helper"
2
+
3
+ require "stringio"
4
+
5
+ class TestCaching < MiniTest::Test
6
+
7
+ class MemoryCache
8
+
9
+ def initialize
10
+ reset
11
+ end
12
+
13
+ def save(&block)
14
+ reset
15
+ yield @io
16
+ end
17
+
18
+ def load(&block)
19
+ @io.rewind
20
+ yield @io
21
+ end
22
+
23
+ private
24
+
25
+ def reset
26
+ @io = StringIO.new
27
+ end
28
+
29
+ end
30
+
31
+ def test_make_defaults
32
+ caching = Rets::Metadata::Caching.make({})
33
+ assert_instance_of Rets::Metadata::NullCache, caching.cache
34
+ assert_instance_of Rets::Metadata::MarshalSerializer, caching.serializer
35
+ end
36
+
37
+ def test_round_trip
38
+ logger = Logger.new("/dev/null")
39
+ sources = {"foo" => "bar"}
40
+ metadata = Rets::Metadata::Root.new(logger, sources)
41
+ caching = Rets::Metadata::Caching.make(metadata_cache: MemoryCache.new)
42
+ caching.save(metadata)
43
+ loaded_metadata = caching.load(logger)
44
+ assert_same logger, loaded_metadata.logger
45
+ assert_equal sources, loaded_metadata.sources
46
+ end
47
+
48
+ def test_load_when_serializer_fails
49
+ serializer = Class.new do
50
+ def load(file)
51
+ nil
52
+ end
53
+ end.new
54
+ logger = Logger.new("/dev/null")
55
+ caching = Rets::Metadata::Caching.make(
56
+ metadata_cache: MemoryCache.new,
57
+ metadata_serializer: serializer,
58
+ )
59
+ loaded_metadata = caching.load(logger)
60
+ assert_nil loaded_metadata
61
+ end
62
+
63
+ def test_load_when_serializer_returns_wrong_type
64
+ serializer = Class.new do
65
+ def load(file)
66
+ Object.new
67
+ end
68
+ end.new
69
+ logger = Logger.new("/dev/null")
70
+ caching = Rets::Metadata::Caching.make(
71
+ metadata_cache: MemoryCache.new,
72
+ metadata_serializer: serializer,
73
+ )
74
+ loaded_metadata = caching.load(logger)
75
+ assert_nil loaded_metadata
76
+ end
77
+
78
+ def test_load_when_cache_fails
79
+ logger = Logger.new("/dev/null")
80
+ cache = stub
81
+ cache.expects(:load).returns(nil)
82
+ caching = Rets::Metadata::Caching.make(
83
+ metadata_cache: cache,
84
+ )
85
+ loaded_metadata = caching.load(logger)
86
+ assert_nil loaded_metadata
87
+ end
88
+
89
+ end
@@ -21,9 +21,15 @@ class TestClient < MiniTest::Test
21
21
  end
22
22
 
23
23
  def test_capability_url_returns_parsed_url
24
- @client.capabilities = { "foo" => "/foo" }
24
+ client = Rets::Client.new(:login_url => "http://example.com", :capabilities => { "foo" => "/foo" })
25
25
 
26
- assert_equal "http://example.com/foo", @client.capability_url("foo")
26
+ assert_equal "http://example.com/foo", client.capability_url("foo")
27
+ end
28
+
29
+ def test_cached_capabilities_case_insensitive
30
+ client = Rets::Client.new(:login_url => "http://example.com", :capabilities => { "foo" => "/foo" })
31
+
32
+ assert_equal client.capabilities.default_proc, Rets::Client::CASE_INSENSITIVE_PROC
27
33
  end
28
34
 
29
35
  def test_capabilities_calls_login_when_nil
@@ -31,6 +37,16 @@ class TestClient < MiniTest::Test
31
37
  @client.capabilities
32
38
  end
33
39
 
40
+ def test_capabilities_does_not_call_login_after_login
41
+ response = mock
42
+ response.stubs(:body).returns(CAPABILITIES)
43
+ @client.stubs(:http_get).returns(response)
44
+ @client.login
45
+
46
+ @client.expects(:login).never
47
+ @client.capabilities
48
+ end
49
+
34
50
  def test_tries_increments_with_each_call
35
51
  assert_equal 1, @client.tries
36
52
  assert_equal 2, @client.tries
@@ -81,6 +97,18 @@ class TestClient < MiniTest::Test
81
97
  logger.debug "foo"
82
98
  end
83
99
 
100
+ def test_find_first_calls_find_every_with_limit_one
101
+ assert_raises ArgumentError do
102
+ @client.find_every({})
103
+ end
104
+ assert_raises ArgumentError do
105
+ @client.find_every(:search_type => "Foo")
106
+ end
107
+ assert_raises ArgumentError do
108
+ @client.find_every(:class => "Bar")
109
+ end
110
+ end
111
+
84
112
  def test_find_first_calls_find_every_with_limit_one
85
113
  @client.expects(:find_every).with({:limit => 1, :foo => :bar}, nil).returns([1,2,3])
86
114
 
@@ -108,7 +136,7 @@ class TestClient < MiniTest::Test
108
136
 
109
137
  Rets::Parser::Compact.expects(:parse_document).with("An ascii string")
110
138
 
111
- @client.find_every({}, false)
139
+ @client.find_every(:search_type => "Foo", :class => "Bar")
112
140
  end
113
141
 
114
142
  def test_response_text_encoding_from_utf_8
@@ -120,7 +148,7 @@ class TestClient < MiniTest::Test
120
148
 
121
149
  Rets::Parser::Compact.expects(:parse_document).with("Some string with non-ascii characters \u0119")
122
150
 
123
- @client.find_every({}, false)
151
+ @client.find_every(:search_type => "Foo", :class => "Bar")
124
152
  end
125
153
 
126
154
  def test_response_text_encoding_from_utf_16
@@ -132,7 +160,7 @@ class TestClient < MiniTest::Test
132
160
 
133
161
  Rets::Parser::Compact.expects(:parse_document).with("Some string with non-utf-8 characters \uFFFD")
134
162
 
135
- @client.find_every({}, false)
163
+ @client.find_every(:search_type => "Foo", :class => "Bar")
136
164
  end
137
165
 
138
166
  def test_find_retries_when_receiving_no_records_found
@@ -155,24 +183,24 @@ class TestClient < MiniTest::Test
155
183
 
156
184
  def test_find_retries_on_errors
157
185
  @client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.raises(Rets::InvalidRequest.new(20134, 'Not Found')).then.returns([])
186
+ @client.stubs(:login)
158
187
  @client.find(:all, :foo => :bar)
159
188
  end
160
189
 
161
- def test_find_retries_on_errors_preserves_resolve
162
- @client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.raises(Rets::InvalidRequest.new(20134, 'Not Found')).then.with({:foo => :bar}, true).returns([])
163
- @client.find(:all, {:foo => :bar, :resolve => true})
164
- end
165
-
166
190
  def test_find_eventually_reraises_errors
167
191
  @client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized'))
192
+ @client.stubs(:login)
193
+
168
194
  assert_raises Rets::AuthorizationFailure do
169
195
  @client.find(:all, :foo => :bar)
170
196
  end
171
197
  end
172
198
 
173
- def test_fixup_keys
174
- assert_equal({ "Foo" => "bar" }, @client.fixup_keys(:foo => "bar"))
175
- assert_equal({ "FooFoo" => "bar" }, @client.fixup_keys(:foo_foo => "bar"))
199
+ def test_find_logs_in_after_auth_error
200
+ @client.stubs(:find_every).raises(Rets::AuthorizationFailure.new(401, 'Not Authorized')).then.returns(["foo"])
201
+
202
+ @client.expects(:login)
203
+ @client.find(:all, :foo => :bar)
176
204
  end
177
205
 
178
206
  def test_all_objects_calls_objects
@@ -189,10 +217,10 @@ class TestClient < MiniTest::Test
189
217
  end
190
218
 
191
219
  def test_objects_handle_array_argument
192
- @client.expects(:fetch_object).with("1,2", :foo => :bar)
220
+ @client.expects(:fetch_object).with("1:2:3", :foo => :bar)
193
221
  @client.stubs(:create_parts_from_response)
194
222
 
195
- @client.objects([1,2], :foo => :bar)
223
+ @client.objects([1,2,3], :foo => :bar)
196
224
  end
197
225
 
198
226
  def test_objects_raises_on_other_arguments
@@ -228,7 +256,7 @@ class TestClient < MiniTest::Test
228
256
  def test_create_parts_from_response_returns_a_single_part_when_not_multipart_response
229
257
  response = {}
230
258
  response.stubs(:header => { "content-type" => ['text/plain']})
231
- response.stubs(:headers => { "content-type" => ['text/plain']})
259
+ response.stubs(:headers => { "Content-Type" => 'text/plain'})
232
260
  response.stubs(:body => "fakebody")
233
261
 
234
262
  parts = @client.create_parts_from_response(response)
@@ -249,14 +277,6 @@ class TestClient < MiniTest::Test
249
277
  assert_equal "foo", @client.object("1", :foo => :bar)
250
278
  end
251
279
 
252
- def test_metadata_caches
253
- metadata = stub(:current? => true)
254
- @client.metadata = metadata
255
- @client.stubs(:capabilities => {})
256
-
257
- assert_same metadata, @client.metadata, "Should be memoized"
258
- end
259
-
260
280
  def test_decorate_result_handles_bad_metadata
261
281
  result = {'foo' => 'bar'}
262
282
  rets_class = stub
@@ -59,6 +59,16 @@ class TestErrorChecker < MiniTest::Test
59
59
  end
60
60
  end
61
61
 
62
+ Rets::Parser::ErrorChecker::INVALID_REQUEST_ERROR_MAPPING.each do |error_code, error_class|
63
+ define_method("test_#{error_class}_failure") do
64
+ response = mock
65
+ response.stubs(:body).returns(error_body_with_code(error_code))
66
+ assert_raises error_class do
67
+ Rets::Parser::ErrorChecker.check(response)
68
+ end
69
+ end
70
+ end
71
+
62
72
  def test_invalid_request_failure
63
73
  response = mock
64
74
  response.stubs(:body).returns(RETS_INVALID_REQUEST_ERROR)
@@ -66,4 +76,12 @@ class TestErrorChecker < MiniTest::Test
66
76
  Rets::Parser::ErrorChecker.check(response)
67
77
  end
68
78
  end
79
+
80
+ def error_body_with_code(code)
81
+ <<-XML
82
+ <?xml version="1.0"?>
83
+ <RETS ReplyCode="#{code}" ReplyText="Error message">
84
+ </RETS>
85
+ XML
86
+ end
69
87
  end
@@ -0,0 +1,42 @@
1
+ require_relative "helper"
2
+
3
+ require "tempfile"
4
+
5
+ class TestFileCache < MiniTest::Test
6
+
7
+ def with_tempfile(&block)
8
+ Tempfile.open(File.basename(__FILE__), &block)
9
+ end
10
+
11
+ def with_temp_path
12
+ with_tempfile do |file|
13
+ path = file.path
14
+ begin
15
+ file.close!
16
+ yield path
17
+ ensure
18
+ File.delete(path)
19
+ end
20
+ end
21
+ end
22
+
23
+ def test_save
24
+ with_temp_path do |path|
25
+ cache = Rets::Metadata::FileCache.new(path)
26
+ cache.save { |file| file.print "foo" }
27
+ file_contents = File.read(path)
28
+ assert_equal "foo", file_contents
29
+ end
30
+ end
31
+
32
+ def test_load
33
+ with_tempfile do |file|
34
+ file.print "foo"
35
+ file.close
36
+ cache = Rets::Metadata::FileCache.new(file.path)
37
+ file_contents = cache.load(&:read)
38
+ assert_equal "foo", file_contents
39
+ end
40
+ end
41
+
42
+ end
@@ -31,66 +31,102 @@ class TestHttpClient < MiniTest::Test
31
31
 
32
32
  assert_equal @http_client.http_post(url, {}), response
33
33
  end
34
+ end
35
+
36
+ class CookieManagement < MiniTest::Test
37
+ def setup
38
+ @logger = Rets::Client::FakeLogger.new
39
+ @logger.stubs(:debug?).returns(false)
40
+
41
+ @cm = WebAgent::CookieManager.new
42
+ http = HTTPClient.new
43
+ http.cookie_manager = @cm
44
+ @client = Rets::HttpClient.new(http, {}, nil, "http://rets.rets.com/somestate/login.aspx")
45
+ end
46
+
47
+ def teardown
48
+ # Empty cookie jar
49
+ @cm.cookies = []
50
+ end
51
+
52
+ def test_http_cookie_with_one_cookie_from_one_domain
53
+ set_cookie = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
54
+ @cm.parse(set_cookie, URI.parse("http://www.rets.rets.com"))
55
+ assert_equal "879392834723043209", @client.http_cookie('RETS-Session-ID')
56
+ end
57
+
58
+ def test_http_cookie_with_multiple_cookies_from_one_domain
59
+ # NOTE: Cookies are ordered alphabetically by name when retrieving
60
+ set_cookie_1 = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
61
+ @cm.parse(set_cookie_1, URI.parse("http://www.rets.rets.com"))
62
+
63
+ set_cookie_2 = "Zoo=Bar; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
64
+ @cm.parse(set_cookie_2, URI.parse("http://www.rets.rets.com"))
65
+
66
+ set_cookie_3 = "Foo=Bar; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
67
+ @cm.parse(set_cookie_3, URI.parse("http://www.rets.rets.com"))
68
+
69
+ assert_equal "879392834723043209", @client.http_cookie('RETS-Session-ID')
70
+ end
71
+
72
+ def test_http_cookie_with_no_cookies_from_domain
73
+ assert_equal nil, @client.http_cookie('RETS-Session-ID')
74
+ end
75
+
76
+ def test_save_cookie_store
77
+ cookie_file = Tempfile.new('cookie_file').path
78
+
79
+ #setup cookie store
80
+ client_a = Rets::HttpClient.from_options({ cookie_store: cookie_file, login_url: "http://rets.rets.com/somestate/login.aspx" }, @logger)
81
+
82
+ #add cookie
83
+ cookie = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
84
+ client_a.http.cookie_manager.parse(cookie, URI.parse("http://www.rets.rets.com"))
85
+
86
+ #save cookie
87
+ client_a.save_cookie_store
88
+
89
+ #create new HTTPCLient with same cookie store
90
+ client_b = Rets::HttpClient.from_options({ cookie_store: cookie_file, login_url: "http://rets.rets.com/somestate/login.aspx" }, @logger)
91
+
92
+ #check added cookie exists
93
+ assert_equal "879392834723043209", client_b.http_cookie('RETS-Session-ID')
94
+ end
95
+
96
+ def test_creates_cookie_store_if_missing_during_initialization
97
+ cookie_file = Tempfile.new('cookie_file')
98
+ cookie_file_path = cookie_file.path
99
+
100
+ #remove cookie store
101
+ cookie_file.unlink
102
+
103
+ client = Rets::HttpClient.from_options({cookie_store: cookie_file_path}, @logger)
104
+
105
+ #add cookie
106
+ cookie = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
107
+ client.http.cookie_manager.parse(cookie, URI.parse("http://www.rets.rets.com"))
108
+
109
+ #check added cookie exists
110
+ assert_equal "879392834723043209", client.http_cookie('RETS-Session-ID')
111
+ end
112
+
113
+ def test_creates_cookie_store_if_missing_during_save
114
+ cookie_file = Tempfile.new('cookie_file')
115
+ cookie_file_path = cookie_file.path
116
+
117
+ client = Rets::HttpClient.from_options({cookie_store: cookie_file_path}, @logger)
118
+
119
+ #remove cookie store
120
+ cookie_file.unlink
121
+
122
+ #add cookie
123
+ cookie = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
124
+ client.http.cookie_manager.parse(cookie, URI.parse("http://www.rets.rets.com"))
125
+
126
+ #save cookie
127
+ client.save_cookie_store
34
128
 
35
- class CookieManagement < MiniTest::Test
36
- def setup
37
- @cm = WebAgent::CookieManager.new
38
- http = HTTPClient.new
39
- http.cookie_manager = @cm
40
- @client = Rets::HttpClient.new(http, {}, nil, "http://rets.rets.com/somestate/login.aspx")
41
- end
42
-
43
- def teardown
44
- # Empty cookie jar
45
- @cm.cookies = []
46
- end
47
-
48
- def test_http_cookie_with_one_cookie_from_one_domain
49
- set_cookie = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
50
- @cm.parse(set_cookie, URI.parse("http://www.rets.rets.com"))
51
- assert_equal "879392834723043209", @client.http_cookie('RETS-Session-ID')
52
- end
53
-
54
- def test_http_cookie_with_multiple_cookies_from_one_domain
55
- # NOTE: Cookies are ordered alphabetically by name when retrieving
56
- set_cookie_1 = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
57
- @cm.parse(set_cookie_1, URI.parse("http://www.rets.rets.com"))
58
-
59
- set_cookie_2 = "Zoo=Bar; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
60
- @cm.parse(set_cookie_2, URI.parse("http://www.rets.rets.com"))
61
-
62
- set_cookie_3 = "Foo=Bar; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
63
- @cm.parse(set_cookie_3, URI.parse("http://www.rets.rets.com"))
64
-
65
- assert_equal "879392834723043209", @client.http_cookie('RETS-Session-ID')
66
- end
67
-
68
- def test_http_cookie_with_no_cookies_from_domain
69
- assert_equal nil, @client.http_cookie('RETS-Session-ID')
70
- end
71
-
72
- def test_save_cookie_store
73
- cookie_file = Tempfile.new('cookie_file').path
74
-
75
- #setup cookie store
76
- http_a = HTTPClient.new
77
- http_a.set_cookie_store(cookie_file)
78
- client_a = Rets::HttpClient.new(http_a, { cookie_store: cookie_file }, nil, "http://rets.rets.com/somestate/login.aspx")
79
-
80
- #add cookie
81
- cookie = "RETS-Session-ID=879392834723043209; path=/; domain=rets.rets.com; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
82
- http_a.cookie_manager.parse(cookie, URI.parse("http://www.rets.rets.com"))
83
-
84
- #save cookie
85
- client_a.save_cookie_store
86
-
87
- #create new HTTPCLient with same cookie store
88
- http_b = HTTPClient.new
89
- http_b.set_cookie_store(cookie_file)
90
- client_b = Rets::HttpClient.new(http_b, { cookie_store: cookie_file }, nil, "http://rets.rets.com/somestate/login.aspx")
91
-
92
- #check added cookie exists
93
- assert_equal "879392834723043209", client_b.http_cookie('RETS-Session-ID')
94
- end
129
+ #check added cookie exists
130
+ assert_equal "879392834723043209", client.http_cookie('RETS-Session-ID')
95
131
  end
96
132
  end