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