spark_api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. data/History.txt +139 -0
  2. data/LICENSE +14 -0
  3. data/README.md +153 -0
  4. data/Rakefile +18 -0
  5. data/VERSION +1 -0
  6. data/bin/spark_api +8 -0
  7. data/bin/spark_api~ +8 -0
  8. data/lib/spark_api.rb +46 -0
  9. data/lib/spark_api/authentication.rb +55 -0
  10. data/lib/spark_api/authentication/api_auth.rb +104 -0
  11. data/lib/spark_api/authentication/api_auth.rb~ +104 -0
  12. data/lib/spark_api/authentication/base_auth.rb +47 -0
  13. data/lib/spark_api/authentication/base_auth.rb~ +47 -0
  14. data/lib/spark_api/authentication/oauth2.rb +198 -0
  15. data/lib/spark_api/authentication/oauth2.rb~ +199 -0
  16. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb +87 -0
  17. data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb~ +87 -0
  18. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb +48 -0
  19. data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb~ +49 -0
  20. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb +44 -0
  21. data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb~ +45 -0
  22. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb +35 -0
  23. data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb~ +36 -0
  24. data/lib/spark_api/authentication/oauth2_impl/middleware.rb +38 -0
  25. data/lib/spark_api/authentication/oauth2_impl/middleware.rb~ +39 -0
  26. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb +24 -0
  27. data/lib/spark_api/authentication/oauth2_impl/password_provider.rb~ +25 -0
  28. data/lib/spark_api/cli.rb +158 -0
  29. data/lib/spark_api/cli.rb~ +158 -0
  30. data/lib/spark_api/cli/api_auth.rb +8 -0
  31. data/lib/spark_api/cli/api_auth.rb~ +8 -0
  32. data/lib/spark_api/cli/oauth2.rb +14 -0
  33. data/lib/spark_api/cli/oauth2.rb~ +14 -0
  34. data/lib/spark_api/cli/setup.rb +47 -0
  35. data/lib/spark_api/cli/setup.rb~ +47 -0
  36. data/lib/spark_api/client.rb +27 -0
  37. data/lib/spark_api/configuration.rb +54 -0
  38. data/lib/spark_api/configuration.rb~ +54 -0
  39. data/lib/spark_api/configuration/yaml.rb +101 -0
  40. data/lib/spark_api/configuration/yaml.rb~ +101 -0
  41. data/lib/spark_api/connection.rb +42 -0
  42. data/lib/spark_api/faraday.rb +64 -0
  43. data/lib/spark_api/faraday.rb~ +64 -0
  44. data/lib/spark_api/models.rb +33 -0
  45. data/lib/spark_api/models.rb~ +33 -0
  46. data/lib/spark_api/models/account.rb +115 -0
  47. data/lib/spark_api/models/account.rb~ +115 -0
  48. data/lib/spark_api/models/base.rb +118 -0
  49. data/lib/spark_api/models/base.rb~ +118 -0
  50. data/lib/spark_api/models/connect_prefs.rb +10 -0
  51. data/lib/spark_api/models/connect_prefs.rb~ +10 -0
  52. data/lib/spark_api/models/constraint.rb +16 -0
  53. data/lib/spark_api/models/constraint.rb~ +16 -0
  54. data/lib/spark_api/models/contact.rb +49 -0
  55. data/lib/spark_api/models/contact.rb~ +49 -0
  56. data/lib/spark_api/models/custom_fields.rb +12 -0
  57. data/lib/spark_api/models/custom_fields.rb~ +12 -0
  58. data/lib/spark_api/models/document.rb +11 -0
  59. data/lib/spark_api/models/document.rb~ +11 -0
  60. data/lib/spark_api/models/finders.rb +45 -0
  61. data/lib/spark_api/models/finders.rb~ +45 -0
  62. data/lib/spark_api/models/idx_link.rb +47 -0
  63. data/lib/spark_api/models/idx_link.rb~ +47 -0
  64. data/lib/spark_api/models/listing.rb +197 -0
  65. data/lib/spark_api/models/listing.rb~ +197 -0
  66. data/lib/spark_api/models/listing_cart.rb +72 -0
  67. data/lib/spark_api/models/listing_cart.rb~ +72 -0
  68. data/lib/spark_api/models/market_statistics.rb +33 -0
  69. data/lib/spark_api/models/market_statistics.rb~ +33 -0
  70. data/lib/spark_api/models/message.rb +21 -0
  71. data/lib/spark_api/models/message.rb~ +21 -0
  72. data/lib/spark_api/models/note.rb +41 -0
  73. data/lib/spark_api/models/note.rb~ +41 -0
  74. data/lib/spark_api/models/notification.rb +42 -0
  75. data/lib/spark_api/models/notification.rb~ +42 -0
  76. data/lib/spark_api/models/open_house.rb +24 -0
  77. data/lib/spark_api/models/open_house.rb~ +24 -0
  78. data/lib/spark_api/models/photo.rb +70 -0
  79. data/lib/spark_api/models/photo.rb~ +70 -0
  80. data/lib/spark_api/models/property_types.rb +7 -0
  81. data/lib/spark_api/models/property_types.rb~ +7 -0
  82. data/lib/spark_api/models/saved_search.rb +16 -0
  83. data/lib/spark_api/models/saved_search.rb~ +16 -0
  84. data/lib/spark_api/models/shared_listing.rb +35 -0
  85. data/lib/spark_api/models/shared_listing.rb~ +35 -0
  86. data/lib/spark_api/models/standard_fields.rb +50 -0
  87. data/lib/spark_api/models/standard_fields.rb~ +50 -0
  88. data/lib/spark_api/models/subresource.rb +19 -0
  89. data/lib/spark_api/models/subresource.rb~ +19 -0
  90. data/lib/spark_api/models/system_info.rb +14 -0
  91. data/lib/spark_api/models/system_info.rb~ +14 -0
  92. data/lib/spark_api/models/tour_of_home.rb +24 -0
  93. data/lib/spark_api/models/tour_of_home.rb~ +24 -0
  94. data/lib/spark_api/models/video.rb +16 -0
  95. data/lib/spark_api/models/video.rb~ +16 -0
  96. data/lib/spark_api/models/virtual_tour.rb +18 -0
  97. data/lib/spark_api/models/virtual_tour.rb~ +18 -0
  98. data/lib/spark_api/multi_client.rb +59 -0
  99. data/lib/spark_api/multi_client.rb~ +59 -0
  100. data/lib/spark_api/paginate.rb +109 -0
  101. data/lib/spark_api/paginate.rb~ +109 -0
  102. data/lib/spark_api/primary_array.rb +29 -0
  103. data/lib/spark_api/primary_array.rb~ +29 -0
  104. data/lib/spark_api/request.rb +96 -0
  105. data/lib/spark_api/request.rb~ +96 -0
  106. data/lib/spark_api/response.rb +70 -0
  107. data/lib/spark_api/response.rb~ +70 -0
  108. data/lib/spark_api/version.rb +4 -0
  109. data/lib/spark_api/version.rb~ +4 -0
  110. data/script/console +6 -0
  111. data/script/console~ +6 -0
  112. data/script/example.rb +27 -0
  113. data/script/example.rb~ +27 -0
  114. data/spec/fixtures/accounts/all.json +160 -0
  115. data/spec/fixtures/accounts/my.json +74 -0
  116. data/spec/fixtures/accounts/my_portal.json +20 -0
  117. data/spec/fixtures/accounts/my_put.json +5 -0
  118. data/spec/fixtures/accounts/my_save.json +5 -0
  119. data/spec/fixtures/accounts/office.json +142 -0
  120. data/spec/fixtures/accounts/password_save.json +6 -0
  121. data/spec/fixtures/authentication_failure.json +7 -0
  122. data/spec/fixtures/base.json +13 -0
  123. data/spec/fixtures/contacts/contacts.json +28 -0
  124. data/spec/fixtures/contacts/my.json +19 -0
  125. data/spec/fixtures/contacts/new.json +11 -0
  126. data/spec/fixtures/contacts/new_empty.json +8 -0
  127. data/spec/fixtures/contacts/new_notify.json +11 -0
  128. data/spec/fixtures/contacts/post.json +10 -0
  129. data/spec/fixtures/contacts/tags.json +11 -0
  130. data/spec/fixtures/count.json +10 -0
  131. data/spec/fixtures/empty.json +3 -0
  132. data/spec/fixtures/errors/expired.json +7 -0
  133. data/spec/fixtures/errors/failure.json +5 -0
  134. data/spec/fixtures/errors/failure_with_constraint.json +17 -0
  135. data/spec/fixtures/errors/failure_with_msg.json +7 -0
  136. data/spec/fixtures/generic_delete.json +1 -0
  137. data/spec/fixtures/generic_failure.json +5 -0
  138. data/spec/fixtures/listing_carts/add_listing.json +13 -0
  139. data/spec/fixtures/listing_carts/add_listing_post.json +5 -0
  140. data/spec/fixtures/listing_carts/empty.json +5 -0
  141. data/spec/fixtures/listing_carts/listing_cart.json +19 -0
  142. data/spec/fixtures/listing_carts/new.json +12 -0
  143. data/spec/fixtures/listing_carts/post.json +10 -0
  144. data/spec/fixtures/listing_carts/remove_listing.json +13 -0
  145. data/spec/fixtures/listings/constraints.json +18 -0
  146. data/spec/fixtures/listings/constraints_with_pagination.json +24 -0
  147. data/spec/fixtures/listings/document_index.json +19 -0
  148. data/spec/fixtures/listings/multiple.json +69 -0
  149. data/spec/fixtures/listings/no_subresources.json +38 -0
  150. data/spec/fixtures/listings/open_houses.json +21 -0
  151. data/spec/fixtures/listings/photos/index.json +469 -0
  152. data/spec/fixtures/listings/photos/new.json +12 -0
  153. data/spec/fixtures/listings/photos/post.json +20 -0
  154. data/spec/fixtures/listings/put.json +5 -0
  155. data/spec/fixtures/listings/put_expiration_date.json +5 -0
  156. data/spec/fixtures/listings/saved_search.json +17 -0
  157. data/spec/fixtures/listings/shared_listing_get.json +14 -0
  158. data/spec/fixtures/listings/shared_listing_new.json +9 -0
  159. data/spec/fixtures/listings/shared_listing_post.json +10 -0
  160. data/spec/fixtures/listings/tour_of_homes.json +23 -0
  161. data/spec/fixtures/listings/videos_index.json +18 -0
  162. data/spec/fixtures/listings/virtual_tours_index.json +42 -0
  163. data/spec/fixtures/listings/with_documents.json +52 -0
  164. data/spec/fixtures/listings/with_permissions.json +44 -0
  165. data/spec/fixtures/listings/with_photos.json +110 -0
  166. data/spec/fixtures/listings/with_supplement.json +39 -0
  167. data/spec/fixtures/listings/with_videos.json +54 -0
  168. data/spec/fixtures/listings/with_vtour.json +48 -0
  169. data/spec/fixtures/logo_fbs.png +0 -0
  170. data/spec/fixtures/messages/new.json +14 -0
  171. data/spec/fixtures/messages/new_empty.json +7 -0
  172. data/spec/fixtures/messages/new_with_recipients.json +15 -0
  173. data/spec/fixtures/messages/post.json +5 -0
  174. data/spec/fixtures/notes/add.json +11 -0
  175. data/spec/fixtures/notes/agent_shared.json +11 -0
  176. data/spec/fixtures/notes/agent_shared_empty.json +7 -0
  177. data/spec/fixtures/notes/new.json +5 -0
  178. data/spec/fixtures/notifications/mark_read.json +1 -0
  179. data/spec/fixtures/notifications/new.json +8 -0
  180. data/spec/fixtures/notifications/new_empty.json +7 -0
  181. data/spec/fixtures/notifications/notifications.json +43 -0
  182. data/spec/fixtures/notifications/post.json +10 -0
  183. data/spec/fixtures/notifications/unread.json +10 -0
  184. data/spec/fixtures/oauth2/access.json +3 -0
  185. data/spec/fixtures/oauth2/access_with_old_refresh.json +5 -0
  186. data/spec/fixtures/oauth2/access_with_refresh.json +5 -0
  187. data/spec/fixtures/oauth2/authorization_code_body.json +7 -0
  188. data/spec/fixtures/oauth2/error.json +3 -0
  189. data/spec/fixtures/oauth2/password_body.json +7 -0
  190. data/spec/fixtures/oauth2/refresh_body.json +7 -0
  191. data/spec/fixtures/oauth2_error.json +3 -0
  192. data/spec/fixtures/property_types/property_types.json +31 -0
  193. data/spec/fixtures/session.json +10 -0
  194. data/spec/fixtures/standardfields/city.json +1031 -0
  195. data/spec/fixtures/standardfields/nearby.json +53 -0
  196. data/spec/fixtures/standardfields/standardfields.json +188 -0
  197. data/spec/fixtures/standardfields/stateorprovince.json +36 -0
  198. data/spec/fixtures/success.json +5 -0
  199. data/spec/json_helper.rb +76 -0
  200. data/spec/mock_helper.rb +124 -0
  201. data/spec/oauth2_helper.rb +68 -0
  202. data/spec/spec_helper.rb +48 -0
  203. data/spec/unit/flexmls_api_spec.rb~ +23 -0
  204. data/spec/unit/spark_api/authentication/api_auth_spec.rb +169 -0
  205. data/spec/unit/spark_api/authentication/api_auth_spec.rb~ +169 -0
  206. data/spec/unit/spark_api/authentication/base_auth_spec.rb +10 -0
  207. data/spec/unit/spark_api/authentication/base_auth_spec.rb~ +10 -0
  208. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb +10 -0
  209. data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb~ +10 -0
  210. data/spec/unit/spark_api/authentication/oauth2_spec.rb +205 -0
  211. data/spec/unit/spark_api/authentication/oauth2_spec.rb~ +205 -0
  212. data/spec/unit/spark_api/authentication_spec.rb +38 -0
  213. data/spec/unit/spark_api/authentication_spec.rb~ +38 -0
  214. data/spec/unit/spark_api/configuration/yaml_spec.rb +72 -0
  215. data/spec/unit/spark_api/configuration/yaml_spec.rb~ +72 -0
  216. data/spec/unit/spark_api/configuration_spec.rb +122 -0
  217. data/spec/unit/spark_api/configuration_spec.rb~ +122 -0
  218. data/spec/unit/spark_api/faraday_spec.rb +90 -0
  219. data/spec/unit/spark_api/faraday_spec.rb~ +90 -0
  220. data/spec/unit/spark_api/models/account_spec.rb +176 -0
  221. data/spec/unit/spark_api/models/base_spec.rb +106 -0
  222. data/spec/unit/spark_api/models/connect_prefs_spec.rb +9 -0
  223. data/spec/unit/spark_api/models/constraint_spec.rb +19 -0
  224. data/spec/unit/spark_api/models/contact_spec.rb +108 -0
  225. data/spec/unit/spark_api/models/contact_spec.rb~ +108 -0
  226. data/spec/unit/spark_api/models/document_spec.rb +32 -0
  227. data/spec/unit/spark_api/models/listing_cart_spec.rb +127 -0
  228. data/spec/unit/spark_api/models/listing_cart_spec.rb~ +127 -0
  229. data/spec/unit/spark_api/models/listing_spec.rb +320 -0
  230. data/spec/unit/spark_api/models/listing_spec.rb~ +320 -0
  231. data/spec/unit/spark_api/models/message_spec.rb +47 -0
  232. data/spec/unit/spark_api/models/message_spec.rb~ +47 -0
  233. data/spec/unit/spark_api/models/note_spec.rb +63 -0
  234. data/spec/unit/spark_api/models/note_spec.rb~ +63 -0
  235. data/spec/unit/spark_api/models/notification_spec.rb +62 -0
  236. data/spec/unit/spark_api/models/notification_spec.rb~ +62 -0
  237. data/spec/unit/spark_api/models/open_house_spec.rb +39 -0
  238. data/spec/unit/spark_api/models/photo_spec.rb +92 -0
  239. data/spec/unit/spark_api/models/property_types_spec.rb +33 -0
  240. data/spec/unit/spark_api/models/saved_search_spec.rb +40 -0
  241. data/spec/unit/spark_api/models/shared_listing_spec.rb +45 -0
  242. data/spec/unit/spark_api/models/shared_listing_spec.rb~ +45 -0
  243. data/spec/unit/spark_api/models/standard_fields_spec.rb +60 -0
  244. data/spec/unit/spark_api/models/system_info_spec.rb +83 -0
  245. data/spec/unit/spark_api/models/tour_of_home_spec.rb +44 -0
  246. data/spec/unit/spark_api/models/video_spec.rb +36 -0
  247. data/spec/unit/spark_api/models/virtual_tour_spec.rb +44 -0
  248. data/spec/unit/spark_api/multi_client_spec.rb +56 -0
  249. data/spec/unit/spark_api/multi_client_spec.rb~ +56 -0
  250. data/spec/unit/spark_api/paginate_spec.rb +224 -0
  251. data/spec/unit/spark_api/paginate_spec.rb~ +224 -0
  252. data/spec/unit/spark_api/primary_array_spec.rb +41 -0
  253. data/spec/unit/spark_api/primary_array_spec.rb~ +41 -0
  254. data/spec/unit/spark_api/request_spec.rb +344 -0
  255. data/spec/unit/spark_api/request_spec.rb~ +344 -0
  256. data/spec/unit/spark_api_spec.rb +23 -0
  257. metadata +725 -0
@@ -0,0 +1,41 @@
1
+ require './spec/spec_helper'
2
+
3
+ class PrimaryModel
4
+ include SparkApi::Primary
5
+ attr_accessor :Primary, :id, :attributes
6
+ def initialize(id, prime = false)
7
+ @id = id
8
+ @Primary = prime
9
+ @attributes = {"Primary" => prime }
10
+ end
11
+ end
12
+
13
+ describe SparkApi::PrimaryArray do
14
+ it "should give me the primary element" do
15
+ a = PrimaryModel.new(1)
16
+ b = PrimaryModel.new(2)
17
+ c = PrimaryModel.new(3)
18
+ d = PrimaryModel.new(4, true)
19
+ e = PrimaryModel.new(5)
20
+ tester = subject.class.new([d,e])
21
+ tester.primary.should eq(d)
22
+ tester = subject.class.new([a,b,c,d,e])
23
+ tester.primary.should eq(d)
24
+ # Note, it doesn't care if there is more than one primary, just returns first in the list.
25
+ b.Primary = true
26
+ tester.primary.should eq(b)
27
+ end
28
+ it "should return nil when there is no primary element" do
29
+ a = PrimaryModel.new(1)
30
+ b = PrimaryModel.new(2)
31
+ c = PrimaryModel.new(3)
32
+ d = PrimaryModel.new(4)
33
+ e = PrimaryModel.new(5)
34
+ tester = subject.class.new([])
35
+ tester.primary.should be(nil)
36
+ tester = subject.class.new([a,b,c,d,e])
37
+ tester.primary.should be(nil)
38
+ end
39
+ end
40
+
41
+
@@ -0,0 +1,41 @@
1
+ require './spec/spec_helper'
2
+
3
+ class PrimaryModel
4
+ include FlexmlsApi::Primary
5
+ attr_accessor :Primary, :id, :attributes
6
+ def initialize(id, prime = false)
7
+ @id = id
8
+ @Primary = prime
9
+ @attributes = {"Primary" => prime }
10
+ end
11
+ end
12
+
13
+ describe FlexmlsApi::PrimaryArray do
14
+ it "should give me the primary element" do
15
+ a = PrimaryModel.new(1)
16
+ b = PrimaryModel.new(2)
17
+ c = PrimaryModel.new(3)
18
+ d = PrimaryModel.new(4, true)
19
+ e = PrimaryModel.new(5)
20
+ tester = subject.class.new([d,e])
21
+ tester.primary.should eq(d)
22
+ tester = subject.class.new([a,b,c,d,e])
23
+ tester.primary.should eq(d)
24
+ # Note, it doesn't care if there is more than one primary, just returns first in the list.
25
+ b.Primary = true
26
+ tester.primary.should eq(b)
27
+ end
28
+ it "should return nil when there is no primary element" do
29
+ a = PrimaryModel.new(1)
30
+ b = PrimaryModel.new(2)
31
+ c = PrimaryModel.new(3)
32
+ d = PrimaryModel.new(4)
33
+ e = PrimaryModel.new(5)
34
+ tester = subject.class.new([])
35
+ tester.primary.should be(nil)
36
+ tester = subject.class.new([a,b,c,d,e])
37
+ tester.primary.should be(nil)
38
+ end
39
+ end
40
+
41
+
@@ -0,0 +1,344 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe SparkApi do
4
+ describe SparkApi::ClientError do
5
+ subject { SparkApi::ClientError.new({:message=>"OMG FAIL", :code=>1234, :status=>500}) }
6
+ it "should print a helpful to_s" do
7
+ subject.to_s.should == "OMG FAIL"
8
+ subject.message.should == "OMG FAIL"
9
+ end
10
+ it "should have an api code" do
11
+ subject.code.should == 1234
12
+ end
13
+ it "should have an http status" do
14
+ subject.status.should == 500
15
+ end
16
+ it "should raise and exception with attached message" do
17
+ expect { raise subject.class, {:message=>"My Message", :code=>1000, :status=>404}}.to raise_error(SparkApi::ClientError) do |e|
18
+ e.message.should == "My Message"
19
+ e.code.should == 1000
20
+ e.status.should == 404
21
+ end
22
+ expect { raise subject.class.new({:message=>"My Message", :code=>1000, :status=>404}) }.to raise_error(SparkApi::ClientError) do |e|
23
+ e.message.should == "My Message"
24
+ e.code.should == 1000
25
+ e.status.should == 404
26
+ end
27
+ expect { raise subject.class.new({:code=>1000, :status=>404}), "My Message"}.to raise_error(SparkApi::ClientError) do |e|
28
+ e.message.should == "My Message"
29
+ e.code.should == 1000
30
+ e.status.should == 404
31
+ end
32
+ expect { raise subject.class, "My Message"}.to raise_error(SparkApi::ClientError) do |e|
33
+ e.message.should == "My Message"
34
+ e.code.should be == nil
35
+ e.status.should be == nil
36
+ end
37
+ end
38
+ end
39
+
40
+ describe SparkApi::ApiResponse do
41
+ it "should asplode if given an invalid or empty response" do
42
+ expect { SparkApi::ApiResponse.new("KABOOOM") }.to raise_error(SparkApi::InvalidResponse)
43
+ expect { SparkApi::ApiResponse.new({"D"=>{}}) }.to raise_error(SparkApi::InvalidResponse)
44
+ end
45
+ it "should have results when successful" do
46
+ r = SparkApi::ApiResponse.new({"D"=>{"Success" => true, "Results" => []}})
47
+ r.success?.should be(true)
48
+ r.results.empty?.should be(true)
49
+ end
50
+ it "should have a message on error" do
51
+ r = SparkApi::ApiResponse.new({"D"=>{"Success" => false, "Message" => "I am a failure."}})
52
+ r.success?.should be(false)
53
+ r.message.should be == "I am a failure."
54
+ end
55
+ end
56
+
57
+ describe SparkApi::Request do
58
+ before(:all) do
59
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
60
+ stub.get('/v1/system?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
61
+ "Success": true,
62
+ "Results": [{
63
+ "Name": "My User",
64
+ "OfficeId": "20070830184014994915000000",
65
+ "Configuration": [],
66
+ "Id": "20101202170654111629000000",
67
+ "MlsId": "20000426143505724628000000",
68
+ "Office": "test office",
69
+ "Mls": "flexmls Web Demonstration Database"
70
+ }]}
71
+ }']
72
+ }
73
+ stub.get('/v1/marketstatistics/price?ApiSig=SignedToken&AuthToken=1234&Options=ActiveAverageListPrice') { [200, {}, '{"D": {
74
+ "Success": true,
75
+ "Results": [{
76
+ "Dates": ["11/1/2010","10/1/2010","9/1/2010","8/1/2010","7/1/2010",
77
+ "6/1/2010","5/1/2010","4/1/2010","3/1/2010","2/1/2010",
78
+ "1/1/2010","12/1/2009"],
79
+ "ActiveAverageListPrice": [100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000]
80
+ }]}
81
+ }']
82
+ }
83
+ stub.post('/v1/contacts?ApiSig=SignedToken&AuthToken=1234', '{"D":{"Contacts":[{"DisplayName":"Wades Contact","PrimaryEmail":"wade11@fbsdata.com"}]}}') { [201, {}, '{"D": {
84
+ "Success": true,
85
+ "Results": [{"ResourceUri": "1000"}]}}']
86
+ }
87
+ stub.put('/v1/contacts/1000?ApiSig=SignedToken&AuthToken=1234', '{"D":{"Contacts":[{"DisplayName":"WLMCEWENS Contact","PrimaryEmail":"wlmcewen789@fbsdata.com"}]}}') { [200, {}, '{"D": {
88
+ "Success": true}}']
89
+ }
90
+ stub.delete('/v1/contacts/1000?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
91
+ "Success": true}}']
92
+ }
93
+ # EXPIRED RESPONSES
94
+ stub.get('/v1/system?ApiSig=SignedToken&AuthToken=EXPIRED') { [401 , {}, '{"D": {
95
+ "Success": false,
96
+ "Message": "Session token has expired",
97
+ "Code": 1020
98
+ }}']
99
+ }
100
+ stub.post('/v1/contacts?ApiSig=SignedToken&AuthToken=EXPIRED', '{"D":{"Contacts":[{"DisplayName":"Wades Contact","PrimaryEmail":"wade11@fbsdata.com"}]}}') { [401 , {}, '{"D": {
101
+ "Success": false,
102
+ "Message": "Session token has expired",
103
+ "Code": 1020
104
+ }}']
105
+ }
106
+ # Test for really long float numbers
107
+ stub.get('/v1/listings/1000?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
108
+ "Success": true,
109
+ "Results": [{
110
+ "ResourceUri":"/v1/listings/20101103161209156282000000",
111
+ "StandardFields":{
112
+ "BuildingAreaTotal":0.000000000000000000000000001,
113
+ "ListPrice":9999999999999999999999999.99
114
+ }
115
+ }]}
116
+ }']
117
+ }
118
+ # TEST escaped paths
119
+ stub.get('/v1/test%20path%20with%20spaces?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
120
+ "Success": true,
121
+ "Results": []
122
+ }
123
+ }']
124
+ }
125
+
126
+ end
127
+ @connection = test_connection(stubs)
128
+ end
129
+
130
+ context "when successfully authenticated" do
131
+ subject do
132
+ class RequestTest
133
+ include SparkApi::Request
134
+
135
+ attr_accessor *SparkApi::Configuration::VALID_OPTION_KEYS
136
+ attr_accessor :authenticator
137
+ def initialize(session)
138
+ @authenticator=MockApiAuthenticator.new(self)
139
+ @authenticator.session=session
140
+ end
141
+ def authenticate()
142
+ raise "Should not be invoked #{@session.inspect}"
143
+ end
144
+ def authenticated?
145
+ true
146
+ end
147
+ def version()
148
+ "v1"
149
+ end
150
+ attr_accessor :connection
151
+ end
152
+ my_s = mock_session()
153
+ r = RequestTest.new(my_s)
154
+ r.connection = @connection
155
+ r
156
+ end
157
+ it "should get a service" do
158
+ subject.get('/system')[0]["Name"].should == "My User"
159
+ end
160
+ it "should get a service with parameters" do
161
+ subject.get('/marketstatistics/price', "Options" => "ActiveAverageListPrice")[0]["ActiveAverageListPrice"].should == [100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000]
162
+ end
163
+ it "should post to a service" do
164
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
165
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
166
+ end
167
+ it "should put to a service" do
168
+ # This is a hypothetical unsupported service action at this time
169
+ data = {"Contacts" => [{"DisplayName"=>"WLMCEWENS Contact","PrimaryEmail"=>"wlmcewen789@fbsdata.com"}]}
170
+ subject.put('/contacts/1000', data).size.should be(0)
171
+ # No validation here, if no error is raised, everything is hunky dory
172
+ end
173
+ it "should delete from a service" do
174
+ # This is a hypothetical unsupported service action at this time
175
+ subject.delete('/contacts/1000').size.should be(0)
176
+ # No validation here, if no error is raised, everything is hunky dory
177
+ end
178
+
179
+ it "should escape a path correctly" do
180
+ subject.get('/test path with spaces').length.should == 0
181
+ # now try this with an already escaped path. Kaboom!
182
+ expect { subject.get('/test%20path%20with%20spaces') }.to raise_error()
183
+ end
184
+
185
+ it "should give me BigDecimal results for large floating point numbers" do
186
+ MultiJson.default_adapter.should eq(:yajl)
187
+ result = subject.get('/listings/1000')[0]
188
+ result["StandardFields"]["BuildingAreaTotal"].should be_a(Float)
189
+ pending("our JSON parser does not support large decimal types. Anyone feel like writing some c code?") do
190
+ result["StandardFields"]["BuildingAreaTotal"].should be_a(BigDecimal)
191
+ number = BigDecimal.new(result["StandardFields"]["BuildingAreaTotal"].to_s)
192
+ number.to_s.should eq(BigDecimal.new("0.000000000000000000000000001").to_s)
193
+ number = BigDecimal.new(result["StandardFields"]["ListPrice"].to_s)
194
+ number.to_s.should eq(BigDecimal.new("9999999999999999999999999.99").to_s)
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ context "when unauthenticated" do
201
+ subject do
202
+ class RequestAuthTest
203
+ include SparkApi::Request
204
+ attr_accessor *SparkApi::Configuration::VALID_OPTION_KEYS
205
+ attr_accessor :authenticator
206
+ def initialize()
207
+ @authenticator=MockApiAuthenticator.new(self)
208
+ end
209
+ def authenticate()
210
+ @authenticator.session ||= mock_session()
211
+ end
212
+ def authenticated?
213
+ @authenticator.authenticated?
214
+ end
215
+ def sign_token(path, params = {}, post_data="")
216
+ "SignedToken"
217
+ end
218
+ def version()
219
+ "v1"
220
+ end
221
+ attr_accessor :connection
222
+ end
223
+ r = RequestAuthTest.new
224
+ r.connection = @connection
225
+ r
226
+ end
227
+ it "should authenticate and then get a service" do
228
+ subject.get('/system')[0]["Name"].should == "My User"
229
+ end
230
+ it "should authenticate and then post to a service" do
231
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
232
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
233
+ end
234
+ end
235
+
236
+ context "when expired" do
237
+ subject do
238
+ class RequestExpiredTest
239
+ include SparkApi::Request
240
+ attr_accessor *SparkApi::Configuration::VALID_OPTION_KEYS
241
+ attr_accessor :authenticator
242
+ def initialize(session)
243
+ @authenticator=MockApiAuthenticator.new(self)
244
+ @authenticator.session=session
245
+ @reauthenticated = false
246
+ end
247
+ def authenticate()
248
+ @reauthenticated = true
249
+ @authenticator.session = mock_session()
250
+ end
251
+ def authenticated?
252
+ @authenticator.authenticated?
253
+ end
254
+
255
+ def sign_token(path, params = {}, post_data="")
256
+ "SignedToken"
257
+ end
258
+ def version()
259
+ "v1"
260
+ end
261
+ def reauthenticated?
262
+ @reauthenticated == true
263
+ end
264
+ attr_accessor :connection
265
+ end
266
+ r = RequestExpiredTest.new(mock_expired_session())
267
+ r.connection = @connection
268
+ r
269
+ end
270
+ it "should reauthenticate and then get a service" do
271
+ subject.get('/system')[0]["Name"].should == "My User"
272
+ subject.reauthenticated?.should == true
273
+ end
274
+ it "should reauthenticate and then post to a service" do
275
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
276
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
277
+ subject.reauthenticated?.should == true
278
+ end
279
+ end
280
+
281
+ context "when expire response" do
282
+ subject do
283
+ session = SparkApi::Authentication::Session.new("AuthToken" => "EXPIRED", "Expires" => (Time.now - 3600).to_s, "Roles" => "['idx']")
284
+ r = RequestExpiredTest.new(session)
285
+ r.connection = @connection
286
+ r
287
+ end
288
+ it "should reauthenticate and then get a service" do
289
+ subject.get('/system')[0]["Name"].should == "My User"
290
+ subject.reauthenticated?.should == true
291
+ end
292
+ it "should reauthenticate and then post to a service" do
293
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
294
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
295
+ subject.reauthenticated?.should == true
296
+ end
297
+ end
298
+
299
+ context "when the server is being a real jerk on expire response" do
300
+ subject do
301
+ class RequestAlwaysExpiredJerkTest
302
+ include SparkApi::Request
303
+ attr_accessor *SparkApi::Configuration::VALID_OPTION_KEYS
304
+ attr_accessor :authenticator
305
+ def initialize()
306
+ @authenticator=MockApiAuthenticator.new(self)
307
+ @reauthenticated = 0
308
+ end
309
+ def authenticate()
310
+ @reauthenticated += 1
311
+ @authenticator.session = SparkApi::Authentication::Session.new("AuthToken" => "EXPIRED", "Expires" => (Time.now + 60).to_s, "Roles" => "['idx']")
312
+ end
313
+ def authenticated?
314
+ @authenticator.authenticated?
315
+ end
316
+ def sign_token(path, params = {}, post_data="")
317
+ "SignedToken"
318
+ end
319
+ def version()
320
+ "v1"
321
+ end
322
+ def reauthenticated
323
+ @reauthenticated
324
+ end
325
+ attr_accessor :connection
326
+ end
327
+ r = RequestAlwaysExpiredJerkTest.new
328
+ r.connection = @connection
329
+ r
330
+ end
331
+ it "should fail horribly on a get" do
332
+ expect { subject.get('/system')}.to raise_error(SparkApi::PermissionDenied){ |e| e.code.should == SparkApi::ResponseCodes::SESSION_TOKEN_EXPIRED }
333
+ subject.reauthenticated.should == 2
334
+ end
335
+ it "should fail horribly on a post" do
336
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
337
+ expect { subject.post('/contacts', data)}.to raise_error(SparkApi::PermissionDenied){ |e| e.code.should == SparkApi::ResponseCodes::SESSION_TOKEN_EXPIRED }
338
+ subject.reauthenticated.should == 2
339
+ end
340
+ end
341
+
342
+ end
343
+
344
+ end
@@ -0,0 +1,344 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe FlexmlsApi do
4
+ describe FlexmlsApi::ClientError do
5
+ subject { FlexmlsApi::ClientError.new({:message=>"OMG FAIL", :code=>1234, :status=>500}) }
6
+ it "should print a helpful to_s" do
7
+ subject.to_s.should == "OMG FAIL"
8
+ subject.message.should == "OMG FAIL"
9
+ end
10
+ it "should have an api code" do
11
+ subject.code.should == 1234
12
+ end
13
+ it "should have an http status" do
14
+ subject.status.should == 500
15
+ end
16
+ it "should raise and exception with attached message" do
17
+ expect { raise subject.class, {:message=>"My Message", :code=>1000, :status=>404}}.to raise_error(FlexmlsApi::ClientError) do |e|
18
+ e.message.should == "My Message"
19
+ e.code.should == 1000
20
+ e.status.should == 404
21
+ end
22
+ expect { raise subject.class.new({:message=>"My Message", :code=>1000, :status=>404}) }.to raise_error(FlexmlsApi::ClientError) do |e|
23
+ e.message.should == "My Message"
24
+ e.code.should == 1000
25
+ e.status.should == 404
26
+ end
27
+ expect { raise subject.class.new({:code=>1000, :status=>404}), "My Message"}.to raise_error(FlexmlsApi::ClientError) do |e|
28
+ e.message.should == "My Message"
29
+ e.code.should == 1000
30
+ e.status.should == 404
31
+ end
32
+ expect { raise subject.class, "My Message"}.to raise_error(FlexmlsApi::ClientError) do |e|
33
+ e.message.should == "My Message"
34
+ e.code.should be == nil
35
+ e.status.should be == nil
36
+ end
37
+ end
38
+ end
39
+
40
+ describe FlexmlsApi::ApiResponse do
41
+ it "should asplode if given an invalid or empty response" do
42
+ expect { FlexmlsApi::ApiResponse.new("KABOOOM") }.to raise_error(FlexmlsApi::InvalidResponse)
43
+ expect { FlexmlsApi::ApiResponse.new({"D"=>{}}) }.to raise_error(FlexmlsApi::InvalidResponse)
44
+ end
45
+ it "should have results when successful" do
46
+ r = FlexmlsApi::ApiResponse.new({"D"=>{"Success" => true, "Results" => []}})
47
+ r.success?.should be(true)
48
+ r.results.empty?.should be(true)
49
+ end
50
+ it "should have a message on error" do
51
+ r = FlexmlsApi::ApiResponse.new({"D"=>{"Success" => false, "Message" => "I am a failure."}})
52
+ r.success?.should be(false)
53
+ r.message.should be == "I am a failure."
54
+ end
55
+ end
56
+
57
+ describe FlexmlsApi::Request do
58
+ before(:all) do
59
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
60
+ stub.get('/v1/system?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
61
+ "Success": true,
62
+ "Results": [{
63
+ "Name": "My User",
64
+ "OfficeId": "20070830184014994915000000",
65
+ "Configuration": [],
66
+ "Id": "20101202170654111629000000",
67
+ "MlsId": "20000426143505724628000000",
68
+ "Office": "test office",
69
+ "Mls": "flexmls Web Demonstration Database"
70
+ }]}
71
+ }']
72
+ }
73
+ stub.get('/v1/marketstatistics/price?ApiSig=SignedToken&AuthToken=1234&Options=ActiveAverageListPrice') { [200, {}, '{"D": {
74
+ "Success": true,
75
+ "Results": [{
76
+ "Dates": ["11/1/2010","10/1/2010","9/1/2010","8/1/2010","7/1/2010",
77
+ "6/1/2010","5/1/2010","4/1/2010","3/1/2010","2/1/2010",
78
+ "1/1/2010","12/1/2009"],
79
+ "ActiveAverageListPrice": [100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000]
80
+ }]}
81
+ }']
82
+ }
83
+ stub.post('/v1/contacts?ApiSig=SignedToken&AuthToken=1234', '{"D":{"Contacts":[{"DisplayName":"Wades Contact","PrimaryEmail":"wade11@fbsdata.com"}]}}') { [201, {}, '{"D": {
84
+ "Success": true,
85
+ "Results": [{"ResourceUri": "1000"}]}}']
86
+ }
87
+ stub.put('/v1/contacts/1000?ApiSig=SignedToken&AuthToken=1234', '{"D":{"Contacts":[{"DisplayName":"WLMCEWENS Contact","PrimaryEmail":"wlmcewen789@fbsdata.com"}]}}') { [200, {}, '{"D": {
88
+ "Success": true}}']
89
+ }
90
+ stub.delete('/v1/contacts/1000?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
91
+ "Success": true}}']
92
+ }
93
+ # EXPIRED RESPONSES
94
+ stub.get('/v1/system?ApiSig=SignedToken&AuthToken=EXPIRED') { [401 , {}, '{"D": {
95
+ "Success": false,
96
+ "Message": "Session token has expired",
97
+ "Code": 1020
98
+ }}']
99
+ }
100
+ stub.post('/v1/contacts?ApiSig=SignedToken&AuthToken=EXPIRED', '{"D":{"Contacts":[{"DisplayName":"Wades Contact","PrimaryEmail":"wade11@fbsdata.com"}]}}') { [401 , {}, '{"D": {
101
+ "Success": false,
102
+ "Message": "Session token has expired",
103
+ "Code": 1020
104
+ }}']
105
+ }
106
+ # Test for really long float numbers
107
+ stub.get('/v1/listings/1000?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
108
+ "Success": true,
109
+ "Results": [{
110
+ "ResourceUri":"/v1/listings/20101103161209156282000000",
111
+ "StandardFields":{
112
+ "BuildingAreaTotal":0.000000000000000000000000001,
113
+ "ListPrice":9999999999999999999999999.99
114
+ }
115
+ }]}
116
+ }']
117
+ }
118
+ # TEST escaped paths
119
+ stub.get('/v1/test%20path%20with%20spaces?ApiSig=SignedToken&AuthToken=1234') { [200, {}, '{"D": {
120
+ "Success": true,
121
+ "Results": []
122
+ }
123
+ }']
124
+ }
125
+
126
+ end
127
+ @connection = test_connection(stubs)
128
+ end
129
+
130
+ context "when successfully authenticated" do
131
+ subject do
132
+ class RequestTest
133
+ include FlexmlsApi::Request
134
+
135
+ attr_accessor *FlexmlsApi::Configuration::VALID_OPTION_KEYS
136
+ attr_accessor :authenticator
137
+ def initialize(session)
138
+ @authenticator=MockApiAuthenticator.new(self)
139
+ @authenticator.session=session
140
+ end
141
+ def authenticate()
142
+ raise "Should not be invoked #{@session.inspect}"
143
+ end
144
+ def authenticated?
145
+ true
146
+ end
147
+ def version()
148
+ "v1"
149
+ end
150
+ attr_accessor :connection
151
+ end
152
+ my_s = mock_session()
153
+ r = RequestTest.new(my_s)
154
+ r.connection = @connection
155
+ r
156
+ end
157
+ it "should get a service" do
158
+ subject.get('/system')[0]["Name"].should == "My User"
159
+ end
160
+ it "should get a service with parameters" do
161
+ subject.get('/marketstatistics/price', "Options" => "ActiveAverageListPrice")[0]["ActiveAverageListPrice"].should == [100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000]
162
+ end
163
+ it "should post to a service" do
164
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
165
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
166
+ end
167
+ it "should put to a service" do
168
+ # This is a hypothetical unsupported service action at this time
169
+ data = {"Contacts" => [{"DisplayName"=>"WLMCEWENS Contact","PrimaryEmail"=>"wlmcewen789@fbsdata.com"}]}
170
+ subject.put('/contacts/1000', data).size.should be(0)
171
+ # No validation here, if no error is raised, everything is hunky dory
172
+ end
173
+ it "should delete from a service" do
174
+ # This is a hypothetical unsupported service action at this time
175
+ subject.delete('/contacts/1000').size.should be(0)
176
+ # No validation here, if no error is raised, everything is hunky dory
177
+ end
178
+
179
+ it "should escape a path correctly" do
180
+ subject.get('/test path with spaces').length.should == 0
181
+ # now try this with an already escaped path. Kaboom!
182
+ expect { subject.get('/test%20path%20with%20spaces') }.to raise_error()
183
+ end
184
+
185
+ it "should give me BigDecimal results for large floating point numbers" do
186
+ MultiJson.default_engine.should eq(:yajl)
187
+ result = subject.get('/listings/1000')[0]
188
+ result["StandardFields"]["BuildingAreaTotal"].should be_a(Float)
189
+ pending("our JSON parser does not support large decimal types. Anyone feel like writing some c code?") do
190
+ result["StandardFields"]["BuildingAreaTotal"].should be_a(BigDecimal)
191
+ number = BigDecimal.new(result["StandardFields"]["BuildingAreaTotal"].to_s)
192
+ number.to_s.should eq(BigDecimal.new("0.000000000000000000000000001").to_s)
193
+ number = BigDecimal.new(result["StandardFields"]["ListPrice"].to_s)
194
+ number.to_s.should eq(BigDecimal.new("9999999999999999999999999.99").to_s)
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ context "when unauthenticated" do
201
+ subject do
202
+ class RequestAuthTest
203
+ include FlexmlsApi::Request
204
+ attr_accessor *FlexmlsApi::Configuration::VALID_OPTION_KEYS
205
+ attr_accessor :authenticator
206
+ def initialize()
207
+ @authenticator=MockApiAuthenticator.new(self)
208
+ end
209
+ def authenticate()
210
+ @authenticator.session ||= mock_session()
211
+ end
212
+ def authenticated?
213
+ @authenticator.authenticated?
214
+ end
215
+ def sign_token(path, params = {}, post_data="")
216
+ "SignedToken"
217
+ end
218
+ def version()
219
+ "v1"
220
+ end
221
+ attr_accessor :connection
222
+ end
223
+ r = RequestAuthTest.new
224
+ r.connection = @connection
225
+ r
226
+ end
227
+ it "should authenticate and then get a service" do
228
+ subject.get('/system')[0]["Name"].should == "My User"
229
+ end
230
+ it "should authenticate and then post to a service" do
231
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
232
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
233
+ end
234
+ end
235
+
236
+ context "when expired" do
237
+ subject do
238
+ class RequestExpiredTest
239
+ include FlexmlsApi::Request
240
+ attr_accessor *FlexmlsApi::Configuration::VALID_OPTION_KEYS
241
+ attr_accessor :authenticator
242
+ def initialize(session)
243
+ @authenticator=MockApiAuthenticator.new(self)
244
+ @authenticator.session=session
245
+ @reauthenticated = false
246
+ end
247
+ def authenticate()
248
+ @reauthenticated = true
249
+ @authenticator.session = mock_session()
250
+ end
251
+ def authenticated?
252
+ @authenticator.authenticated?
253
+ end
254
+
255
+ def sign_token(path, params = {}, post_data="")
256
+ "SignedToken"
257
+ end
258
+ def version()
259
+ "v1"
260
+ end
261
+ def reauthenticated?
262
+ @reauthenticated == true
263
+ end
264
+ attr_accessor :connection
265
+ end
266
+ r = RequestExpiredTest.new(mock_expired_session())
267
+ r.connection = @connection
268
+ r
269
+ end
270
+ it "should reauthenticate and then get a service" do
271
+ subject.get('/system')[0]["Name"].should == "My User"
272
+ subject.reauthenticated?.should == true
273
+ end
274
+ it "should reauthenticate and then post to a service" do
275
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
276
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
277
+ subject.reauthenticated?.should == true
278
+ end
279
+ end
280
+
281
+ context "when expire response" do
282
+ subject do
283
+ session = FlexmlsApi::Authentication::Session.new("AuthToken" => "EXPIRED", "Expires" => (Time.now - 3600).to_s, "Roles" => "['idx']")
284
+ r = RequestExpiredTest.new(session)
285
+ r.connection = @connection
286
+ r
287
+ end
288
+ it "should reauthenticate and then get a service" do
289
+ subject.get('/system')[0]["Name"].should == "My User"
290
+ subject.reauthenticated?.should == true
291
+ end
292
+ it "should reauthenticate and then post to a service" do
293
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
294
+ subject.post('/contacts', data)[0]["ResourceUri"].should == "1000"
295
+ subject.reauthenticated?.should == true
296
+ end
297
+ end
298
+
299
+ context "when the server is being a real jerk on expire response" do
300
+ subject do
301
+ class RequestAlwaysExpiredJerkTest
302
+ include FlexmlsApi::Request
303
+ attr_accessor *FlexmlsApi::Configuration::VALID_OPTION_KEYS
304
+ attr_accessor :authenticator
305
+ def initialize()
306
+ @authenticator=MockApiAuthenticator.new(self)
307
+ @reauthenticated = 0
308
+ end
309
+ def authenticate()
310
+ @reauthenticated += 1
311
+ @authenticator.session = FlexmlsApi::Authentication::Session.new("AuthToken" => "EXPIRED", "Expires" => (Time.now + 60).to_s, "Roles" => "['idx']")
312
+ end
313
+ def authenticated?
314
+ @authenticator.authenticated?
315
+ end
316
+ def sign_token(path, params = {}, post_data="")
317
+ "SignedToken"
318
+ end
319
+ def version()
320
+ "v1"
321
+ end
322
+ def reauthenticated
323
+ @reauthenticated
324
+ end
325
+ attr_accessor :connection
326
+ end
327
+ r = RequestAlwaysExpiredJerkTest.new
328
+ r.connection = @connection
329
+ r
330
+ end
331
+ it "should fail horribly on a get" do
332
+ expect { subject.get('/system')}.to raise_error(FlexmlsApi::PermissionDenied){ |e| e.code.should == FlexmlsApi::ResponseCodes::SESSION_TOKEN_EXPIRED }
333
+ subject.reauthenticated.should == 2
334
+ end
335
+ it "should fail horribly on a post" do
336
+ data = {"Contacts" => [{"DisplayName"=>"Wades Contact","PrimaryEmail"=>"wade11@fbsdata.com"}]}
337
+ expect { subject.post('/contacts', data)}.to raise_error(FlexmlsApi::PermissionDenied){ |e| e.code.should == FlexmlsApi::ResponseCodes::SESSION_TOKEN_EXPIRED }
338
+ subject.reauthenticated.should == 2
339
+ end
340
+ end
341
+
342
+ end
343
+
344
+ end