rets-hack 0.11
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +142 -0
- data/Manifest.txt +58 -0
- data/README.md +129 -0
- data/Rakefile +28 -0
- data/bin/rets +202 -0
- data/example/connect.rb +19 -0
- data/example/get-photos.rb +20 -0
- data/example/get-property.rb +16 -0
- data/lib/rets/client.rb +373 -0
- data/lib/rets/client_progress_reporter.rb +48 -0
- data/lib/rets/http_client.rb +133 -0
- data/lib/rets/locking_http_client.rb +34 -0
- data/lib/rets/measuring_http_client.rb +27 -0
- data/lib/rets/metadata/caching.rb +59 -0
- data/lib/rets/metadata/containers.rb +89 -0
- data/lib/rets/metadata/file_cache.rb +29 -0
- data/lib/rets/metadata/json_serializer.rb +27 -0
- data/lib/rets/metadata/lookup_table.rb +65 -0
- data/lib/rets/metadata/lookup_type.rb +19 -0
- data/lib/rets/metadata/marshal_serializer.rb +27 -0
- data/lib/rets/metadata/multi_lookup_table.rb +70 -0
- data/lib/rets/metadata/null_cache.rb +24 -0
- data/lib/rets/metadata/resource.rb +103 -0
- data/lib/rets/metadata/rets_class.rb +57 -0
- data/lib/rets/metadata/rets_object.rb +41 -0
- data/lib/rets/metadata/root.rb +155 -0
- data/lib/rets/metadata/table.rb +33 -0
- data/lib/rets/metadata/table_factory.rb +19 -0
- data/lib/rets/metadata/yaml_serializer.rb +27 -0
- data/lib/rets/metadata.rb +18 -0
- data/lib/rets/parser/compact.rb +117 -0
- data/lib/rets/parser/error_checker.rb +56 -0
- data/lib/rets/parser/multipart.rb +39 -0
- data/lib/rets.rb +269 -0
- data/test/fixtures.rb +324 -0
- data/test/helper.rb +14 -0
- data/test/test_caching.rb +89 -0
- data/test/test_client.rb +307 -0
- data/test/test_error_checker.rb +87 -0
- data/test/test_file_cache.rb +42 -0
- data/test/test_http_client.rb +132 -0
- data/test/test_json_serializer.rb +26 -0
- data/test/test_locking_http_client.rb +29 -0
- data/test/test_marshal_serializer.rb +26 -0
- data/test/test_metadata.rb +71 -0
- data/test/test_metadata_class.rb +50 -0
- data/test/test_metadata_lookup_table.rb +21 -0
- data/test/test_metadata_lookup_type.rb +21 -0
- data/test/test_metadata_multi_lookup_table.rb +60 -0
- data/test/test_metadata_object.rb +33 -0
- data/test/test_metadata_resource.rb +148 -0
- data/test/test_metadata_root.rb +151 -0
- data/test/test_metadata_table.rb +21 -0
- data/test/test_metadata_table_factory.rb +24 -0
- data/test/test_parser_compact.rb +115 -0
- data/test/test_parser_multipart.rb +39 -0
- data/test/test_yaml_serializer.rb +26 -0
- data/test/vcr_cassettes/unauthorized_response.yml +262 -0
- 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’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&#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 <tag> 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 &#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,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
|