rets-hack 0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/Manifest.txt +58 -0
  4. data/README.md +129 -0
  5. data/Rakefile +28 -0
  6. data/bin/rets +202 -0
  7. data/example/connect.rb +19 -0
  8. data/example/get-photos.rb +20 -0
  9. data/example/get-property.rb +16 -0
  10. data/lib/rets/client.rb +373 -0
  11. data/lib/rets/client_progress_reporter.rb +48 -0
  12. data/lib/rets/http_client.rb +133 -0
  13. data/lib/rets/locking_http_client.rb +34 -0
  14. data/lib/rets/measuring_http_client.rb +27 -0
  15. data/lib/rets/metadata/caching.rb +59 -0
  16. data/lib/rets/metadata/containers.rb +89 -0
  17. data/lib/rets/metadata/file_cache.rb +29 -0
  18. data/lib/rets/metadata/json_serializer.rb +27 -0
  19. data/lib/rets/metadata/lookup_table.rb +65 -0
  20. data/lib/rets/metadata/lookup_type.rb +19 -0
  21. data/lib/rets/metadata/marshal_serializer.rb +27 -0
  22. data/lib/rets/metadata/multi_lookup_table.rb +70 -0
  23. data/lib/rets/metadata/null_cache.rb +24 -0
  24. data/lib/rets/metadata/resource.rb +103 -0
  25. data/lib/rets/metadata/rets_class.rb +57 -0
  26. data/lib/rets/metadata/rets_object.rb +41 -0
  27. data/lib/rets/metadata/root.rb +155 -0
  28. data/lib/rets/metadata/table.rb +33 -0
  29. data/lib/rets/metadata/table_factory.rb +19 -0
  30. data/lib/rets/metadata/yaml_serializer.rb +27 -0
  31. data/lib/rets/metadata.rb +18 -0
  32. data/lib/rets/parser/compact.rb +117 -0
  33. data/lib/rets/parser/error_checker.rb +56 -0
  34. data/lib/rets/parser/multipart.rb +39 -0
  35. data/lib/rets.rb +269 -0
  36. data/test/fixtures.rb +324 -0
  37. data/test/helper.rb +14 -0
  38. data/test/test_caching.rb +89 -0
  39. data/test/test_client.rb +307 -0
  40. data/test/test_error_checker.rb +87 -0
  41. data/test/test_file_cache.rb +42 -0
  42. data/test/test_http_client.rb +132 -0
  43. data/test/test_json_serializer.rb +26 -0
  44. data/test/test_locking_http_client.rb +29 -0
  45. data/test/test_marshal_serializer.rb +26 -0
  46. data/test/test_metadata.rb +71 -0
  47. data/test/test_metadata_class.rb +50 -0
  48. data/test/test_metadata_lookup_table.rb +21 -0
  49. data/test/test_metadata_lookup_type.rb +21 -0
  50. data/test/test_metadata_multi_lookup_table.rb +60 -0
  51. data/test/test_metadata_object.rb +33 -0
  52. data/test/test_metadata_resource.rb +148 -0
  53. data/test/test_metadata_root.rb +151 -0
  54. data/test/test_metadata_table.rb +21 -0
  55. data/test/test_metadata_table_factory.rb +24 -0
  56. data/test/test_parser_compact.rb +115 -0
  57. data/test/test_parser_multipart.rb +39 -0
  58. data/test/test_yaml_serializer.rb +26 -0
  59. data/test/vcr_cassettes/unauthorized_response.yml +262 -0
  60. metadata +227 -0
data/lib/rets.rb ADDED
@@ -0,0 +1,269 @@
1
+ require 'uri'
2
+ require 'digest/md5'
3
+ require 'nokogiri'
4
+
5
+ module Rets
6
+ VERSION = '0.11.0'
7
+
8
+ HttpError = Class.new(StandardError)
9
+ MalformedResponse = Class.new(ArgumentError)
10
+ UnknownResponse = Class.new(ArgumentError)
11
+ NoLogout = Class.new(ArgumentError)
12
+
13
+ class AuthorizationFailure < ArgumentError
14
+ attr_reader :status, :body
15
+ def initialize(status, body)
16
+ @status = status
17
+ @body = body
18
+ super("HTTP status: #{status} (#{body})")
19
+ end
20
+ end
21
+
22
+ class NoRecordsFound < ArgumentError
23
+ ERROR_CODE = 20201
24
+ attr_reader :reply_text
25
+
26
+ def initialize(reply_text)
27
+ @reply_text = reply_text
28
+ super("Got error code #{ERROR_CODE} (#{reply_text})")
29
+ end
30
+ end
31
+
32
+ class NoObjectFound < ArgumentError
33
+ ERROR_CODE = 20403
34
+ attr_reader :reply_text
35
+
36
+ def initialize(reply_text)
37
+ @reply_text = reply_text
38
+ super("Got error code #{ERROR_CODE} (#{reply_text})")
39
+ end
40
+ end
41
+
42
+ class InvalidRequest < ArgumentError
43
+ attr_reader :error_code, :reply_text
44
+ def initialize(error_code, reply_text)
45
+ @error_code = error_code
46
+ @reply_text = reply_text
47
+ super("Got error code #{error_code} (#{reply_text})")
48
+ end
49
+ end
50
+
51
+ class UnknownCapability < ArgumentError
52
+ attr_reader :capability_name, :capabilities
53
+ def initialize(capability_name, capabilities=[])
54
+ @capability_name = capability_name
55
+ @capabilities = capabilities
56
+ super("unknown capability #{capability_name}, available capabilities #{capabilities}")
57
+ end
58
+ end
59
+
60
+ class SystemError < InvalidRequest
61
+ ERROR_CODE = 10000
62
+ end
63
+
64
+ class ZeroBalance < InvalidRequest
65
+ ERROR_CODE = 20003
66
+ end
67
+
68
+ class BrokerCodeRequired < InvalidRequest
69
+ ERROR_CODE = 20012
70
+ end
71
+
72
+ class AdditionalLoginNotPermitted < InvalidRequest
73
+ ERROR_CODE = 20022
74
+ end
75
+
76
+ class ServerLoginError < InvalidRequest
77
+ ERROR_CODE = 20036
78
+ end
79
+
80
+ class ClientAuthenticationFailed < InvalidRequest
81
+ ERROR_CODE = 20037
82
+ end
83
+
84
+ class UserAgentAuthenticationRequired < InvalidRequest
85
+ ERROR_CODE = 20041
86
+ end
87
+
88
+ class ServerTemporarilyDisabled < InvalidRequest
89
+ ERROR_CODE = 20050
90
+ end
91
+
92
+ class InsecurePassword < InvalidRequest
93
+ ERROR_CODE = 20140
94
+ end
95
+
96
+ class SameAsPreviousPassword < InvalidRequest
97
+ ERROR_CODE = 20141
98
+ end
99
+
100
+ class InvalidUserName < InvalidRequest
101
+ ERROR_CODE = 20142
102
+ end
103
+
104
+ class UnknownQueryField < InvalidRequest
105
+ ERROR_CODE = 20200
106
+ end
107
+
108
+ class InvalidSelect < InvalidRequest
109
+ ERROR_CODE = 20202
110
+ end
111
+
112
+ class MiscellaneousSearchError < InvalidRequest
113
+ ERROR_CODE = 20203
114
+ end
115
+
116
+ class InvalidQuerySyntax < InvalidRequest
117
+ ERROR_CODE = 20206
118
+ end
119
+
120
+ class UnauthorizedQuery < InvalidRequest
121
+ ERROR_CODE = 20207
122
+ end
123
+
124
+ class MaximumRecordsExceeded < InvalidRequest
125
+ ERROR_CODE = 20208
126
+ end
127
+
128
+ class RequestTimeout < InvalidRequest
129
+ ERROR_CODE = 20209
130
+ end
131
+
132
+ class TooManyOutstandingQueries < InvalidRequest
133
+ ERROR_CODE = 20210
134
+ end
135
+
136
+ class QueryTooComplex < InvalidRequest
137
+ ERROR_CODE = 20211
138
+ end
139
+
140
+ class InvalidKeyRequest < InvalidRequest
141
+ ERROR_CODE = 20212
142
+ end
143
+
144
+ class InvalidKey < InvalidRequest
145
+ ERROR_CODE = 20213
146
+ end
147
+
148
+ class InvalidParameter < InvalidRequest
149
+ ERROR_CODE = 20301
150
+ end
151
+
152
+ class UnableToSaveRecord < InvalidRequest
153
+ ERROR_CODE = 20301
154
+ end
155
+
156
+ class MiscellaneousUpdateError < InvalidRequest
157
+ ERROR_CODE = 20301
158
+ end
159
+
160
+ class WarningResponseNotGivenForAllWarnings < InvalidRequest
161
+ ERROR_CODE = 20311
162
+ end
163
+
164
+ class WarningResponseGivenForWarningNotRequired < InvalidRequest
165
+ ERROR_CODE = 20312
166
+ end
167
+
168
+ class InvalidResource < InvalidRequest
169
+ ERROR_CODE = 20400
170
+ end
171
+
172
+ class InvalidType < InvalidRequest
173
+ ERROR_CODE = 20401
174
+ end
175
+
176
+ class InvalidIdentifier < InvalidRequest
177
+ ERROR_CODE = 20402
178
+ end
179
+
180
+ class UnsupportedMimeType < InvalidRequest
181
+ ERROR_CODE = 20406
182
+ end
183
+
184
+ class UnauthorizedRetrieval < InvalidRequest
185
+ ERROR_CODE = 20407
186
+ end
187
+
188
+ class ResourceUnavailable < InvalidRequest
189
+ ERROR_CODE = 20408
190
+ end
191
+
192
+ class ObjectUnavailable < InvalidRequest
193
+ ERROR_CODE = 20409
194
+ end
195
+
196
+ class RequestTooLarge < InvalidRequest
197
+ ERROR_CODE = 20410
198
+ end
199
+
200
+ class ExecutionTimeout < InvalidRequest
201
+ ERROR_CODE = 20411
202
+ end
203
+
204
+ class TooManyOutstandingRequests < InvalidRequest
205
+ ERROR_CODE = 20412
206
+ end
207
+
208
+ class InvalidResourceRequested < InvalidRequest
209
+ ERROR_CODE = 20500
210
+ end
211
+
212
+ class InvalidMetadataType < InvalidRequest
213
+ ERROR_CODE = 20501
214
+ end
215
+
216
+ class InvalidIdentifierRequested < InvalidRequest
217
+ ERROR_CODE = 20502
218
+ end
219
+
220
+ class NoMetadataFound < InvalidRequest
221
+ ERROR_CODE = 20503
222
+ end
223
+
224
+ class UnsupportedMetadataMimeType < InvalidRequest
225
+ ERROR_CODE = 20506
226
+ end
227
+
228
+ class UnauthorizedMetadataRetrieval < InvalidRequest
229
+ ERROR_CODE = 20507
230
+ end
231
+
232
+ class MetadataResourceUnavailable < InvalidRequest
233
+ ERROR_CODE = 20508
234
+ end
235
+
236
+ class MetadataUnavailable < InvalidRequest
237
+ ERROR_CODE = 20509
238
+ end
239
+
240
+ class MetadataRequestTooLarge < InvalidRequest
241
+ ERROR_CODE = 20510
242
+ end
243
+
244
+ class MetadataRequestTimeout < InvalidRequest
245
+ ERROR_CODE = 20511
246
+ end
247
+
248
+ class TooManyOutstandingMetadataRequests < InvalidRequest
249
+ ERROR_CODE = 20512
250
+ end
251
+
252
+ class RequestedDTDVersionUnavailable < InvalidRequest
253
+ ERROR_CODE = 20514
254
+ end
255
+
256
+ class NotLoggedIn < InvalidRequest
257
+ ERROR_CODE = 20701
258
+ end
259
+ end
260
+
261
+ require 'rets/http_client'
262
+ require 'rets/client'
263
+ require 'rets/metadata'
264
+ require 'rets/parser/error_checker'
265
+ require 'rets/parser/compact'
266
+ require 'rets/parser/multipart'
267
+ require 'rets/measuring_http_client'
268
+ require 'rets/locking_http_client'
269
+ require 'rets/client_progress_reporter'
data/test/fixtures.rb ADDED
@@ -0,0 +1,324 @@
1
+ RETS_NO_RECORDS_ERROR = <<-XML
2
+ <?xml version="1.0"?>
3
+ <RETS ReplyCode="20201" ReplyText="Error message">
4
+ </RETS>
5
+ XML
6
+
7
+ RETS_NO_OBJECT_ERROR = <<-XML
8
+ <?xml version="1.0"?>
9
+ <RETS ReplyCode="20403" ReplyText="Error message">
10
+ </RETS>
11
+ XML
12
+
13
+ RETS_INVALID_REQUEST_ERROR = <<-XML
14
+ <?xml version="1.0"?>
15
+ <RETS ReplyCode="123" ReplyText="Error message">
16
+ </RETS>
17
+ XML
18
+
19
+ RETS_REPLY = <<-XML
20
+ <?xml version="1.0"?>
21
+ <RETS ReplyCode="0" ReplyText="OK">
22
+ </RETS>
23
+ XML
24
+
25
+ CAPABILITIES = <<-XML
26
+ <RETS ReplyCode="0" ReplyText="OK">
27
+ <RETS-RESPONSE>
28
+
29
+ Abc=123
30
+ Def=ghi=jk
31
+
32
+ </RETS-RESPONSE>
33
+ </RETS>
34
+ XML
35
+
36
+ COUNT_ONLY = <<XML
37
+ <RETS ReplyCode="0" ReplyText="Success">
38
+ <COUNT Records="1234" />
39
+ </RETS>
40
+ XML
41
+
42
+ RETS_STATUS_NO_MATCHING_RECORDS = <<XML
43
+ <?xml version="1.0"?>
44
+ <RETS ReplyCode="0" ReplyText="Operation Successful">
45
+ <RETS-STATUS ReplyCode="20201" ReplyText="No matching records were found" />
46
+ </RETS>
47
+ XML
48
+
49
+ CAPABILITIES_WITH_WHITESPACE = <<XML
50
+ <RETS ReplyCode="0" ReplyText="Operation Successful">
51
+ <RETS-RESPONSE>
52
+ Action = /RETS/Action
53
+ </RETS-RESPONSE>
54
+ </RETS>
55
+ XML
56
+
57
+ # 44 is the ASCII code for comma; an invalid delimiter.
58
+ INVALID_DELIMETER = <<-XML
59
+ <?xml version="1.0"?>
60
+ <METADATA-RESOURCE Version="01.72.10306" Date="2011-03-15T19:51:22">
61
+ <DELIMITER value="44" />
62
+ <COLUMNS>A\tB</COLUMNS>
63
+ <DATA>1\t2</DATA>
64
+ <DATA>4\t5</DATA>
65
+ </METADATA>
66
+ XML
67
+
68
+ CHANGED_DELIMITER = <<-XML
69
+ <?xml version="1.0"?>
70
+ <METADATA-RESOURCE Version="01.72.10306" Date="2011-03-15T19:51:22">
71
+ <DELIMITER value="45" />
72
+ <COLUMNS>A-B</COLUMNS>
73
+ <DATA>1-2</DATA>
74
+ <DATA>4-5</DATA>
75
+ </METADATA>
76
+ XML
77
+
78
+ COMPACT = <<-XML
79
+ <?xml version="1.0"?>
80
+ <METADATA-RESOURCE Version="01.72.10306" Date="2011-03-15T19:51:22">
81
+ <COLUMNS>A\tB</COLUMNS>
82
+ <DATA>1\t2</DATA>
83
+ <DATA>4\t5</DATA>
84
+ </METADATA>
85
+ XML
86
+
87
+
88
+ EMPTY_COMPACT = <<-XML
89
+ <METADATA-TABLE Resource="OpenHouse" Class="OpenHouse" Version="01.01.00000" Date="2011-07-29T12:09:16">
90
+ </METADATA-TABLE>
91
+ XML
92
+
93
+ METADATA_UNKNOWN = <<-XML
94
+ <?xml version="1.0"?>
95
+ <RETS ReplyCode="0" ReplyText="Operation successful.">
96
+ <METADATA-FOO Version="01.72.10306" Date="2011-03-15T19:51:22">
97
+ <UNKNOWN />
98
+ </METADATA-FOO>
99
+ XML
100
+
101
+ METADATA_SYSTEM = <<-XML
102
+ <?xml version="1.0"?>
103
+ <RETS ReplyCode="0" ReplyText="Operation successful.">
104
+ <METADATA-SYSTEM Version="01.72.10306" Date="2011-03-15T19:51:22">
105
+ <SYSTEM />
106
+ <COMMENTS />
107
+ </METADATA-SYSTEM>
108
+ XML
109
+
110
+ METADATA_RESOURCE = <<-XML
111
+ <?xml version="1.0"?>
112
+ <RETS ReplyCode="0" ReplyText="Operation successful.">
113
+ <METADATA-RESOURCE Version="01.72.10306" Date="2011-03-15T19:51:22">
114
+ <COLUMNS> ResourceID StandardName VisibleName Description KeyField ClassCount ClassVersion ClassDate ObjectVersion ObjectDate SearchHelpVersion SearchHelpDate EditMaskVersion EditMaskDate LookupVersion LookupDate UpdateHelpVersion UpdateHelpDate ValidationExpressionVersion ValidationExpressionDate ValidationLookupVersionValidationLookupDate ValidationExternalVersion ValidationExternalDate </COLUMNS>
115
+ <DATA> ActiveAgent ActiveAgent Active Agent Search Contains information about active agents. MemberNumber 1 01.72.10304 2011-03-03T00:29:23 01.72.10000 2010-08-16T15:08:20 01.72.10305 2011-03-09T21:33:41 01.72.10284 2011-02-24T06:56:43 </DATA>
116
+ <DATA> Agent Agent Agent Search Contains information about all agents. MemberNumber 1 01.72.10303 2011-03-03T00:29:23 01.72.10000 2010-08-16T15:08:20 01.72.10305 2011-03-09T21:33:41 01.72.10284 2011-02-24T06:56:43 </DATA>
117
+ <DATA> History History History Search Contains information about accumulated changes to each listing. TransactionRid 1 01.72.10185 2010-12-02T02:02:58 01.72.10000 2010-08-16T15:08:20 01.72.10000 2010-08-16T22:08:30 01.72.10000 2010-08-16T15:08:20 </DATA>
118
+ <DATA> MemberAssociation Member Association Contains MLS member Association information. MemberAssociationKey 1 01.72.10277 2011-02-23T19:11:10 01.72.10214 2011-01-06T16:41:05 01.72.10220 2011-01-06T16:41:06 </DATA>
119
+ <DATA> Office Office Office Search Contains information about broker offices. OfficeNumber 1 01.72.10302 2011-03-03T00:29:23 01.72.10000 2010-08-16T15:08:20 01.72.10305 2011-03-09T21:33:41 01.72.10284 2011-02-24T06:56:43 </DATA>
120
+ <DATA> OfficeAssociation Office Association Contains MLS office Association information. OfficeAssociationKey 1 01.72.10306 2011-03-15T19:51:22 01.72.10245 2011-01-06T16:41:08 01.72.10251 2011-01-06T16:41:08 </DATA>
121
+ <DATA> OpenHouse OpenHouse Open House Search Contains information about public open house activities. OpenHouseRid 1 01.72.10185 2010-12-02T02:02:58 01.72.10000 2010-08-16T15:08:20 01.72.10134 2010-11-12T13:57:32 01.72.10000 2010-08-16T15:08:20 </DATA>
122
+ <DATA> Property Property Property Search Contains information about listed properties. ListingRid 8 01.72.10288 2011-02-24T06:59:11 01.72.10000 2010-08-16T15:08:20 01.72.10289 2011-02-24T06:59:19 01.72.10290 2011-03-01T11:06:31 </DATA>
123
+ <DATA> PropertyDeleted Deleted Property Search Contains information about deleted properties. ListingRid 1 01.72.10185 2010-12-02T02:02:58 01.72.10000 2010-08-16T15:08:20 01.72.10000 2010-08-16T22:08:30 01.72.10000 2010-08-16T22:08:34 </DATA>
124
+ <DATA> PropertyWithheld Withheld Property Search Contains information about withheld properties. ListingRid 8 01.72.10201 2011-01-05T19:34:36 01.72.10000 2010-08-16T15:08:20 01.72.10200 2011-01-05T19:34:34 01.72.10000 2010-08-16T22:08:34 </DATA>
125
+ <DATA> Prospect Prospect Prospect Search Contains information about sales or listing propects. ProspectRid 1 01.72.10185 2010-12-02T02:02:58 01.72.10000 2010-08-16T15:08:20 01.72.10000 2010-08-16T15:08:20 01.72.10000 2010-08-16T15:08:20 </DATA>
126
+ <DATA> Tour Tour Tour Search Contains information about private tour activities. TourRid 1 01.72.10185 2010-12-02T02:02:58 01.72.10000 2010-08-16T15:08:20 01.72.10000 2010-08-16T22:08:30 01.72.10000 2010-08-16T15:08:20 </DATA>
127
+ <DATA> VirtualMedia Virtual Media Contains information about virtual media for MLS listings. VirtualMediaRid 1 01.72.10126 2010-11-12T13:47:41 01.72.10127 2010-11-12T13:47:41 01.72.10086 2010-11-10T09:59:11 </DATA>
128
+ </METADATA-RESOURCE>
129
+ </RETS>
130
+ XML
131
+
132
+ METADATA_OBJECT = "<RETS ReplyCode=\"0\" ReplyText=\"V2.6.0 728: Success\">\r\n<METADATA-OBJECT Resource=\"Property\" Version=\"1.12.24\" Date=\"Wed, 1 Dec 2010 00:00:00 GMT\">\r\n<COLUMNS>\tMetadataEntryID\tObjectType\tStandardName\tMimeType\tVisibleName\tDescription\tObjectTimeStamp\tObjectCount\t</COLUMNS>\r\n<DATA>\t50045650619\tMedium\tMedium\timage/jpeg\tMedium\tA 320 x 240 Size Photo\tLastPhotoDate\tTotalPhotoCount\t</DATA>\r\n<DATA>\t20101753230\tDocumentPDF\tDocumentPDF\tapplication/pdf\tDocumentPDF\tDocumentPDF\t\t\t</DATA>\r\n<DATA>\t50045650620\tPhoto\tPhoto\timage/jpeg\tPhoto\tA 640 x 480 Size Photo\tLastPhotoDate\tTotalPhotoCount\t</DATA>\r\n<DATA>\t50045650621\tThumbnail\tThumbnail\timage/jpeg\tThumbnail\tA 128 x 96 Size Photo\tLastPhotoDate\tTotalPhotoCount\t</DATA>\r\n</METADATA-OBJECT>\r\n</RETS>\r\n"
133
+
134
+ MULITPART_RESPONSE = [
135
+ "--simple boundary",
136
+ "Content-Type: image/jpeg",
137
+ "Content-Length: 10",
138
+ "Content-ID: 90020062739",
139
+ "Object-ID: 1",
140
+ "",
141
+ "xxxxxxxx",
142
+ "--simple boundary",
143
+ "Content-Type: image/jpeg",
144
+ "Content-Length: 10",
145
+ "Content-ID: 90020062739",
146
+ "Object-ID: 2",
147
+ "",
148
+ "yyyyyyyy",
149
+ "--simple boundary",
150
+ ""
151
+ ].join("\r\n")
152
+
153
+ MULTIPART_RESPONSE_URLS = [
154
+ '--rets.object.content.boundary.1330546052739',
155
+ 'Content-ID: 38845440',
156
+ 'Object-ID: 1',
157
+ 'Content-Type: text/xml',
158
+ 'Location: http://foobarmls.com/RETS//MediaDisplay/98/hr2890998-1.jpg',
159
+ '',
160
+ '<RETS ReplyCode="0" ReplyText="Operation Successful" />',
161
+ '',
162
+ '--rets.object.content.boundary.1330546052739',
163
+ 'Content-ID: 38845440',
164
+ 'Object-ID: 2',
165
+ 'Content-Type: text/xml',
166
+ 'Location: http://foobarmls.com/RETS//MediaDisplay/98/hr2890998-2.jpg',
167
+ '',
168
+ '<RETS ReplyCode="0" ReplyText="Operation Successful" />',
169
+ '',
170
+ '--rets.object.content.boundary.1330546052739',
171
+ 'Content-ID: 38845440',
172
+ 'Object-ID: 3',
173
+ 'Content-Type: text/xml',
174
+ 'Location: http://foobarmls.com/RETS//MediaDisplay/98/hr2890998-3.jpg',
175
+ '',
176
+ '<RETS ReplyCode="0" ReplyText="Operation Successful" />',
177
+ '',
178
+ '--rets.object.content.boundary.1330546052739',
179
+ 'Content-ID: 38845440',
180
+ 'Object-ID: 4',
181
+ 'Content-Type: text/xml',
182
+ 'Location: http://foobarmls.com/RETS//MediaDisplay/98/hr2890998-4.jpg',
183
+ '',
184
+ '<RETS ReplyCode="0" ReplyText="Operation Successful" />',
185
+ '',
186
+ '--rets.object.content.boundary.1330546052739',
187
+ 'Content-ID: 38845440',
188
+ 'Object-ID: 5',
189
+ 'Content-Type: text/xml',
190
+ 'Location: http://foobarmls.com/RETS//MediaDisplay/98/hr2890998-5.jpg',
191
+ '',
192
+ '<RETS ReplyCode="0" ReplyText="Operation Successful" />',
193
+ '',
194
+ '--rets.object.content.boundary.1330546052739--'
195
+ ].join("\r\n")
196
+
197
+ SAMPLE_COMPACT = <<XML
198
+ <RETS ReplyCode="0" ReplyText="Operation successful.">
199
+ <METADATA-TABLE Resource="ActiveAgent" Class="MEMB" Version="01.72.10236" Date="2011-03-03T00:29:23">
200
+ <COLUMNS> MetadataEntryID SystemName StandardName LongName DBName ShortName MaximumLength DataType Precision Searchable Interpretation Alignment UseSeparator EditMaskID LookupName MaxSelect Units Index Minimum Maximum Default Required SearchHelpID Unique ModTimeStamp ForeignKeyName ForeignField InKeyindex </COLUMNS>
201
+ <DATA> 7 City City City City 11 Character 0 1 Left 0 0 0 0 0 0 0 City 0 0 1 MemberNumber 0 </DATA>
202
+ <DATA> 9 ContactAddlPhoneType1 Contact Additional Phone Type 1 AddlPhTyp1 Contact Addl Ph Type 1 1 Character 0 1 Lookup Left 0 ContactAddlPhoneType 0 0 0 0 0 0 ContactAddlPhoneType 0 0 1 MemberNumber 0 </DATA>
203
+ <DATA> 11 ContactAddlPhoneType2 Contact Additional Phone Type 2 AddlPhTyp2 Contact Addl Ph Type 2 1 Character 0 1 Lookup Left 0 ContactAddlPhoneType 0 0 0 0 0 0 ContactAddlPhoneType 0 0 1 MemberNumber 0 </DATA>
204
+ <DATA> 13 ContactAddlPhoneType3 Contact Additional Phone Type 3 AddlPhTyp3 Contact Addl Ph Type 3 1 Character 0 1 Lookup Left 0 ContactAddlPhoneType 0 0 0 0 0 0 ContactAddlPhoneType 0 0 1 MemberNumber 0 </DATA>
205
+ <DATA> 15 ContactPhoneAreaCode1 Contact Phone Area Code 1 ContPhAC1 Contact Phone AC 1 3 Character 0 1 Left 0 0 0 0 0 0 0 ContactPhoneAreaCode 0 0 1 MemberNumber 0 </DATA>
206
+ <DATA> 17 ContactPhoneAreaCode2 Contact Phone Area Code 2 ContPhAC2 Contact Phone AC 2 3 Character 0 1 Left 0 0 0 0 0 0 0 ContactPhoneAreaCode 0 0 1 MemberNumber 0 </DATA>
207
+ <DATA> 19 ContactPhoneAreaCode3 Contact Phone Area Code 3 ContPhAC3 Contact Phone AC 3 3 Character 0 1 Left 0 0 0 0 0 0 0 ContactPhoneAreaCode 0 0 1 MemberNumber 0 </DATA>
208
+ </METADATA-TABLE>
209
+ </RETS>
210
+ XML
211
+
212
+ SAMPLE_COMPACT_2 = <<XML
213
+ <?xml version="1.0" encoding="utf-8"?>
214
+ <RETS ReplyCode="0" ReplyText="Success">
215
+ <METADATA-TABLE Class="15" Date="2010-10-28T05:41:31Z" Resource="Office" Version="26.27.62891">
216
+ <COLUMNS> ModTimeStamp MetadataEntryID SystemName StandardName LongName DBName ShortName MaximumLength DataType Precision Searchable Interpretation Alignment UseSeparator EditMaskID LookupName MaxSelect Units Index Minimum Maximum Default Required SearchHelpID Unique ForeignKeyName ForeignField InKeyIndex </COLUMNS>
217
+ <DATA> sysid15 sysid sysid sysid sysid 10 Int 0 1 0 0 1 0 0 1 1 </DATA>
218
+ <DATA> 15n1155 OfficePhone_f1155 OfficePhone Phone Offic_1155 Phone 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
219
+ <DATA> 15n1158 AccessFlag_f1158 Office Status Acces_1158 Status 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
220
+ <DATA> 15n1163 MODIFIED_f1163 Modified MODIF_1163 Modified 20 DateTime 0 1 0 0 0 0 0 0 0 </DATA>
221
+ <DATA> 15n1165 DESREALTOR_f1165 DesRealtor DESRE_1165 DesRealtor 75 Character 0 1 0 0 0 0 0 0 0 </DATA>
222
+ <DATA> 15n1166 DESREALTORUID_f1166 Designated Realtor Uid DESRE_1166 RealtorUid 20 Character 0 1 0 0 0 0 0 0 0 </DATA>
223
+ <DATA> 15n1167 INT_NO_f1167 Internet Syndication (Y/N) INT_N_1167 Int.Syn. 1 Character 0 1 Lookup 0 YESNO 1 0 0 0 0 0 </DATA>
224
+ <DATA> 15n1168 STATE_f1168 State STATE_1168 State 50 Character 0 1 Lookup 0 1_523 1 0 0 0 0 0 </DATA>
225
+ <DATA> 15n1169 CITY_f1169 City CITY_1169 City 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
226
+ <DATA> 15n1170 IDX_NO_f1170 IDX (Y/N) IDX_N_1170 IDX 1 Character 0 1 0 0 0 0 0 0 0 </DATA>
227
+ <DATA> 15n1172 ZipCode_f1172 Zip ZipCo_1172 Zip 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
228
+ <DATA> 15n1177 ADDRESS1_f1177 Address Line 1 ADDRE_1177 Address1 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
229
+ <DATA> 15n1182 MLSYN_f1182 MLS Y/N MLSYN_1182 MLSYN 1 Character 0 1 0 0 0 0 0 0 0 </DATA>
230
+ <DATA> 15n1184 OFFICENAME_f1184 Office Name Office&#x2019;s Name OFFIC_1184 Office Name 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
231
+ <DATA> 15n1193 OfficeCode_f1193 OfficeID Office Code Offic_1193 Office Code 12 Character 0 1 0 0 0 0 0 0 1 </DATA>
232
+ </METADATA-TABLE>
233
+ </RETS>
234
+ XML
235
+
236
+ HTML_AUTH_FAILURE = <<EOF
237
+ <html><head><title>Apache Tomcat/6.0.26 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 401 - </h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u></u></p><p><b>description</b> <u>This request requires HTTP authentication ().</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.26</h3></body></html>
238
+ EOF
239
+
240
+ XHTML_AUTH_FAILURE = <<EOF
241
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
242
+ <html xmlns="http://www.w3.org/1999/xhtml">
243
+ <head>
244
+ <title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
245
+ </head>
246
+ <body>
247
+ <h1>401 - Unauthorized: Access is denied due to invalid credentials.</h1>
248
+ <p>You do not have permission to view this directory or page using the credentials that you supplied.</p>
249
+ </body>
250
+ </html>
251
+ EOF
252
+
253
+ SAMPLE_COMPACT_WITH_SPECIAL_CHARS = <<EOF
254
+ <RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
255
+ <DELIMITER value=\"09\" />
256
+ <COLUMNS> PublicRemarksNew WindowCoverings YearBuilt Zoning ZoningCompatibleYN </COLUMNS>
257
+ <DATA> porte-coch&amp;#xE8;re welcomes 1999 00 </DATA>
258
+ </RETS>
259
+ EOF
260
+
261
+ SAMPLE_COMPACT_WITH_SPECIAL_CHARS_2 = <<EOF
262
+ <RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
263
+ <DELIMITER value=\"09\" />
264
+ <COLUMNS> PublicRemarksNew WindowCoverings YearBuilt Zoning ZoningCompatibleYN </COLUMNS>
265
+ <DATA> text with &lt;tag&gt; 1999 00 </DATA>
266
+ </RETS>
267
+ EOF
268
+
269
+ SAMPLE_COMPACT_WITH_DOUBLY_ENCODED_BAD_CHARACTER_REFERENCES = <<EOF
270
+ <RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
271
+ <DELIMITER value=\"09\" />
272
+ <COLUMNS> PublicRemarksNew WindowCoverings YearBuilt Zoning ZoningCompatibleYN </COLUMNS>
273
+ <DATA> foo &amp;#56324; bar 1999 00 </DATA>
274
+ </RETS>
275
+ EOF
276
+
277
+ SAMPLE_PROPERTY_WITH_LOTS_OF_COLUMNS = <<EOF
278
+ <RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
279
+ <DELIMITER value=\"09\" />
280
+ <COLUMNS>\t#{800.times.map { |x| "K%03d" % x }.join("\t") }\t</COLUMNS>
281
+ <DATA>\t\t</DATA>
282
+ </RETS>
283
+ EOF
284
+
285
+ EXAMPLE_METADATA_TREE = <<EOF
286
+ # Resource: Properties (Key Field: matrix_unique_key)
287
+ ## Class: T100
288
+ Visible Name: Prop
289
+ Description : some description
290
+ ### Table: L_1
291
+ Resource: Properties
292
+ ShortName: Sq
293
+ LongName: Square Footage
294
+ StandardName: Sqft
295
+ Units: Meters
296
+ Searchable: Y
297
+ Required: N
298
+ ### LookupTable: L_10
299
+ Resource: Properties
300
+ Required: N
301
+ Searchable: Y
302
+ Units:
303
+ ShortName: HF
304
+ LongName: HOA Frequency
305
+ StandardName: HOA F
306
+ #### Types:
307
+ Quarterly -> Q
308
+ Annually -> A
309
+ ### MultiLookupTable: L_11
310
+ Resource: Properties
311
+ Required: N
312
+ Searchable: Y
313
+ Units:
314
+ ShortName: HFs
315
+ LongName: HOA Frequencies
316
+ StandardName: HOA Fs
317
+ Types:
318
+ Quarterly -> Q
319
+ Annually -> A
320
+ Object: HiRes
321
+ Visible Name: Photo
322
+ Mime Type: photo/jpg
323
+ Description: photo description
324
+ EOF
data/test/helper.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "minitest"
2
+ require "mocha/setup"
3
+
4
+ require_relative "../lib/rets"
5
+
6
+ require_relative "fixtures"
7
+
8
+ require 'vcr'
9
+
10
+ VCR.configure do |c|
11
+ c.cassette_library_dir = 'test/vcr_cassettes'
12
+ c.hook_into :webmock
13
+ end
14
+
@@ -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