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,10 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe SparkApi::Authentication::BaseAuth do
4
+ subject {SparkApi::Authentication::BaseAuth.new(nil) }
5
+ it "should raise an error" do
6
+ expect {subject.authenticate()}.to raise_error(){ |e| e.message.should == "Implement me!"}
7
+ expect {subject.logout()}.to raise_error(){ |e| e.message.should == "Implement me!"}
8
+ expect {subject.request(nil, nil, nil, nil)}.to raise_error(){ |e| e.message.should == "Implement me!"}
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe FlexmlsApi::Authentication::BaseAuth do
4
+ subject {FlexmlsApi::Authentication::BaseAuth.new(nil) }
5
+ it "should raise an error" do
6
+ expect {subject.authenticate()}.to raise_error(){ |e| e.message.should == "Implement me!"}
7
+ expect {subject.logout()}.to raise_error(){ |e| e.message.should == "Implement me!"}
8
+ expect {subject.request(nil, nil, nil, nil)}.to raise_error(){ |e| e.message.should == "Implement me!"}
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require './spec/spec_helper'
2
+ require './spec/oauth2_helper'
3
+
4
+ describe SparkApi::Authentication::OAuth2Impl::GrantTypeBase do
5
+ subject { SparkApi::Authentication::OAuth2Impl::GrantTypeBase }
6
+ # Make sure the client boostraps the right plugin based on configuration.
7
+ it "create should " do
8
+ expect {subject.create(nil, InvalidAuth2Provider.new())}.to raise_error(SparkApi::ClientError){ |e| e.message.should == "Unsupported grant type [not_a_real_type]" }
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require './spec/spec_helper'
2
+ require './spec/oauth2_helper'
3
+
4
+ describe FlexmlsApi::Authentication::OAuth2Impl::GrantTypeBase do
5
+ subject { FlexmlsApi::Authentication::OAuth2Impl::GrantTypeBase }
6
+ # Make sure the client boostraps the right plugin based on configuration.
7
+ it "create should " do
8
+ expect {subject.create(nil, InvalidAuth2Provider.new())}.to raise_error(FlexmlsApi::ClientError){ |e| e.message.should == "Unsupported grant type [not_a_real_type]" }
9
+ end
10
+ end
@@ -0,0 +1,205 @@
1
+ require './spec/spec_helper'
2
+ require './spec/oauth2_helper'
3
+
4
+ describe SparkApi::Authentication::OAuth2 do
5
+ let(:provider) { TestOAuth2Provider.new() }
6
+ let(:client) { SparkApi::Client.new({:authentication_mode => SparkApi::Authentication::OAuth2,:oauth2_provider => provider}) }
7
+ subject {client.authenticator }
8
+ # Make sure the client boostraps the right plugin based on configuration.
9
+ describe "plugin" do
10
+ it "should load the oauth2 authenticator" do
11
+ client.authenticator.class.should eq(SparkApi::Authentication::OAuth2)
12
+ end
13
+ end
14
+ describe "authenticate" do
15
+ it "should authenticate the api credentials" do
16
+ stub_request(:post, provider.access_uri).
17
+ with(:body =>
18
+ '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}'
19
+ ).
20
+ to_return(:body => fixture("oauth2/access.json"), :status=>200)
21
+ subject.authenticate.access_token.should eq("04u7h-4cc355-70k3n")
22
+ subject.authenticate.expires_in.should eq(7200)
23
+ end
24
+
25
+ it "should raise an error when api credentials are invalid" do
26
+ s=stub_request(:post, provider.access_uri).
27
+ with(:body =>
28
+ '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}'
29
+ ).
30
+ to_return(:body => fixture("oauth2/error.json"), :status=>400)
31
+ expect {subject.authenticate()}.to raise_error(SparkApi::ClientError){ |e| e.status.should == 400 }
32
+ end
33
+
34
+ end
35
+
36
+ describe "authenticated?" do
37
+ let(:session) { Object.new }
38
+ it "should return true when session is active" do
39
+ subject.session = session
40
+ session.stub(:expired?) { false }
41
+ subject.authenticated?.should eq(true)
42
+ end
43
+ it "should return false when session is expired" do
44
+ subject.session = session
45
+ session.stub(:expired?) { true }
46
+ subject.authenticated?.should eq(false)
47
+ end
48
+ it "should return false when session is uninitialized" do
49
+ subject.authenticated?.should eq(false)
50
+ end
51
+ end
52
+
53
+
54
+ describe "logout" do
55
+ let(:session) { mock_oauth_session }
56
+ it "should logout when there is an active session" do
57
+ subject.session = session
58
+ subject.logout
59
+ subject.session.should eq(nil)
60
+ end
61
+ it "should skip logging out when there is no active session information" do
62
+ client.stub(:delete) { raise "Should not be called" }
63
+ subject.logout.should eq(nil)
64
+ end
65
+ end
66
+
67
+ describe "request" do
68
+ let(:session) { mock_oauth_session }
69
+ it "should handle a get request" do
70
+ subject.session = session
71
+ args = {
72
+ :_limit => '10',
73
+ :_page => '1',
74
+ :_pagination => '1'
75
+ }
76
+ c = stub_request(:get, "https://api.sparkapi.com/#{SparkApi.version}/listings").
77
+ with(:query => args).
78
+ to_return(:body => fixture("listings/no_subresources.json"))
79
+ subject.session = session
80
+ subject.request(:get, "/#{SparkApi.version}/listings", nil, args).status.should eq(200)
81
+ end
82
+ it "should handle a post request" do
83
+ subject.session = session
84
+ args = {}
85
+ contact = '{"D":{"Contacts":[{"DisplayName":"Contact Four","PrimaryEmail":"contact4@fbsdata.com"}]}}'
86
+ stub_request(:post, "https://api.sparkapi.com/#{SparkApi.version}/contacts").
87
+ with(:body => contact).
88
+ to_return(:body => '{"D": {
89
+ "Success": true,
90
+ "Results": [
91
+ {
92
+ "ResourceUri":"/v1/contacts/20101230223226074204000000"
93
+ }]}
94
+ }',
95
+ :status=>201)
96
+ subject.request(:post, "/#{SparkApi.version}/contacts", contact, args).status.should eq(201)
97
+ end
98
+ end
99
+
100
+ context "with an expired session" do
101
+ context "and a valid refresh token" do
102
+ it "should reset the session and reauthenticate" do
103
+ count = 0
104
+ refresh_count = 0
105
+ stub_request(:post, provider.access_uri).
106
+ with(:body => '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}').
107
+ to_return do
108
+ count += 1
109
+ {:body => fixture("oauth2/access_with_old_refresh.json"), :status=>200}
110
+ end
111
+ stub_request(:post, provider.access_uri).
112
+ with(:body => '{"client_id":"example-id","client_secret":"example-password","grant_type":"refresh_token","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","refresh_token":"0ld-r3fr35h-70k3n"}').
113
+ to_return do
114
+ refresh_count += 1
115
+ {:body => fixture("oauth2/access_with_refresh.json"), :status=>200}
116
+ end
117
+ # Make sure the auth request goes out twice.
118
+ # Fail the first time, but then return the correct value after reauthentication
119
+ stub_request(:get, "https://api.sparkapi.com/#{SparkApi.version}/listings/1234").
120
+ to_return(:body => fixture('errors/expired.json'), :status => 401).times(1).then.
121
+ to_return(:body => fixture('listings/with_documents.json'))
122
+ client.get("/listings/1234")
123
+ count.should eq(1)
124
+ refresh_count.should eq(1)
125
+ client.session.expired?.should eq(false)
126
+ end
127
+ end
128
+ context "and an invalid refresh token" do
129
+ it "should reset the session and reauthenticate" do
130
+ count = 0
131
+ stub_request(:post, provider.access_uri).
132
+ with(:body => '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}').
133
+ to_return do
134
+ count += 1
135
+ {:body => fixture("oauth2/access.json"), :status=>200}
136
+ end
137
+ # Make sure the auth request goes out twice.
138
+ # Fail the first time, but then return the correct value after reauthentication
139
+ stub_request(:get, "https://api.sparkapi.com/#{SparkApi.version}/listings/1234").
140
+ to_return(:body => fixture('errors/expired.json'), :status => 401).times(1).then.
141
+ to_return(:body => fixture('listings/with_documents.json'))
142
+
143
+ client.get("/listings/1234")
144
+ count.should eq(2)
145
+ client.session.expired?.should eq(false)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe SparkApi::Authentication::BaseOAuth2Provider do
152
+ context "session_timeout" do
153
+ it "should provide a default" do
154
+ subject.session_timeout.should eq(86400)
155
+ end
156
+ describe TestOAuth2Provider do
157
+ subject { TestOAuth2Provider.new }
158
+ it "should be able to override the session timeout" do
159
+ subject.session_timeout.should eq(7200)
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "password authentication" do
166
+ let(:provider) { TestCLIOAuth2Provider.new() }
167
+ let(:client) { SparkApi::Client.new({:authentication_mode => SparkApi::Authentication::OAuth2,:oauth2_provider => provider}) }
168
+ subject {client.authenticator }
169
+ it "should authenticate the api credentials with username and password" do
170
+ stub_request(:post, provider.access_uri).
171
+ with(:body =>
172
+ '{"username":"example-user","client_secret":"example-secret","client_id":"example-id","password":"example-password","grant_type":"password"}'
173
+ ).
174
+ to_return(:body => fixture("oauth2/access.json"), :status=>200)
175
+ subject.authenticate.access_token.should eq("04u7h-4cc355-70k3n")
176
+ subject.authenticate.expires_in.should eq(60)
177
+ end
178
+ end
179
+ describe SparkApi::Authentication::OAuth2Impl do
180
+ it "should load a provider" do
181
+ example = "SparkApi::Authentication::OAuth2Impl::PasswordProvider"
182
+ SparkApi::Authentication::OAuth2Impl.load_provider(example,{}).class.to_s.should eq(example)
183
+ prefix = "::#{example}"
184
+ SparkApi::Authentication::OAuth2Impl.load_provider(prefix,{}).class.to_s.should eq(example)
185
+ bad_example = "Derp::Derp::Derp::DerpProvider"
186
+ expect{SparkApi::Authentication::OAuth2Impl.load_provider(bad_example,{}).class.to_s.should eq(bad_example)}.to raise_error(ArgumentError)
187
+ end
188
+
189
+ end
190
+
191
+ describe SparkApi::Authentication::OAuthSession do
192
+ it "should serialize to json" do
193
+ args = {
194
+ "access_token" => "abc",
195
+ "expires_in" => 3600,
196
+ "refresh_token" => "123",
197
+ "refresh_timeout" => 10000,
198
+ "start_time" => "2012-01-01T00:00:00+00:00"
199
+ }
200
+ session = SparkApi::Authentication::OAuthSession.new(args)
201
+ session.start_time.should eq(DateTime.parse(args["start_time"]))
202
+ JSON.parse(session.to_json).should eq(args)
203
+
204
+ end
205
+ end
@@ -0,0 +1,205 @@
1
+ require './spec/spec_helper'
2
+ require './spec/oauth2_helper'
3
+
4
+ describe FlexmlsApi::Authentication::OAuth2 do
5
+ let(:provider) { TestOAuth2Provider.new() }
6
+ let(:client) { FlexmlsApi::Client.new({:authentication_mode => FlexmlsApi::Authentication::OAuth2,:oauth2_provider => provider}) }
7
+ subject {client.authenticator }
8
+ # Make sure the client boostraps the right plugin based on configuration.
9
+ describe "plugin" do
10
+ it "should load the oauth2 authenticator" do
11
+ client.authenticator.class.should eq(FlexmlsApi::Authentication::OAuth2)
12
+ end
13
+ end
14
+ describe "authenticate" do
15
+ it "should authenticate the api credentials" do
16
+ stub_request(:post, provider.access_uri).
17
+ with(:body =>
18
+ '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}'
19
+ ).
20
+ to_return(:body => fixture("oauth2/access.json"), :status=>200)
21
+ subject.authenticate.access_token.should eq("04u7h-4cc355-70k3n")
22
+ subject.authenticate.expires_in.should eq(7200)
23
+ end
24
+
25
+ it "should raise an error when api credentials are invalid" do
26
+ s=stub_request(:post, provider.access_uri).
27
+ with(:body =>
28
+ '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}'
29
+ ).
30
+ to_return(:body => fixture("oauth2/error.json"), :status=>400)
31
+ expect {subject.authenticate()}.to raise_error(FlexmlsApi::ClientError){ |e| e.status.should == 400 }
32
+ end
33
+
34
+ end
35
+
36
+ describe "authenticated?" do
37
+ let(:session) { Object.new }
38
+ it "should return true when session is active" do
39
+ subject.session = session
40
+ session.stub(:expired?) { false }
41
+ subject.authenticated?.should eq(true)
42
+ end
43
+ it "should return false when session is expired" do
44
+ subject.session = session
45
+ session.stub(:expired?) { true }
46
+ subject.authenticated?.should eq(false)
47
+ end
48
+ it "should return false when session is uninitialized" do
49
+ subject.authenticated?.should eq(false)
50
+ end
51
+ end
52
+
53
+
54
+ describe "logout" do
55
+ let(:session) { mock_oauth_session }
56
+ it "should logout when there is an active session" do
57
+ subject.session = session
58
+ subject.logout
59
+ subject.session.should eq(nil)
60
+ end
61
+ it "should skip logging out when there is no active session information" do
62
+ client.stub(:delete) { raise "Should not be called" }
63
+ subject.logout.should eq(nil)
64
+ end
65
+ end
66
+
67
+ describe "request" do
68
+ let(:session) { mock_oauth_session }
69
+ it "should handle a get request" do
70
+ subject.session = session
71
+ args = {
72
+ :_limit => '10',
73
+ :_page => '1',
74
+ :_pagination => '1'
75
+ }
76
+ c = stub_request(:get, "https://api.flexmls.com/#{FlexmlsApi.version}/listings").
77
+ with(:query => args).
78
+ to_return(:body => fixture("listings/no_subresources.json"))
79
+ subject.session = session
80
+ subject.request(:get, "/#{FlexmlsApi.version}/listings", nil, args).status.should eq(200)
81
+ end
82
+ it "should handle a post request" do
83
+ subject.session = session
84
+ args = {}
85
+ contact = '{"D":{"Contacts":[{"DisplayName":"Contact Four","PrimaryEmail":"contact4@fbsdata.com"}]}}'
86
+ stub_request(:post, "https://api.flexmls.com/#{FlexmlsApi.version}/contacts").
87
+ with(:body => contact).
88
+ to_return(:body => '{"D": {
89
+ "Success": true,
90
+ "Results": [
91
+ {
92
+ "ResourceUri":"/v1/contacts/20101230223226074204000000"
93
+ }]}
94
+ }',
95
+ :status=>201)
96
+ subject.request(:post, "/#{FlexmlsApi.version}/contacts", contact, args).status.should eq(201)
97
+ end
98
+ end
99
+
100
+ context "with an expired session" do
101
+ context "and a valid refresh token" do
102
+ it "should reset the session and reauthenticate" do
103
+ count = 0
104
+ refresh_count = 0
105
+ stub_request(:post, provider.access_uri).
106
+ with(:body => '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}').
107
+ to_return do
108
+ count += 1
109
+ {:body => fixture("oauth2/access_with_old_refresh.json"), :status=>200}
110
+ end
111
+ stub_request(:post, provider.access_uri).
112
+ with(:body => '{"client_id":"example-id","client_secret":"example-password","grant_type":"refresh_token","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","refresh_token":"0ld-r3fr35h-70k3n"}').
113
+ to_return do
114
+ refresh_count += 1
115
+ {:body => fixture("oauth2/access_with_refresh.json"), :status=>200}
116
+ end
117
+ # Make sure the auth request goes out twice.
118
+ # Fail the first time, but then return the correct value after reauthentication
119
+ stub_request(:get, "https://api.flexmls.com/#{FlexmlsApi.version}/listings/1234").
120
+ to_return(:body => fixture('errors/expired.json'), :status => 401).times(1).then.
121
+ to_return(:body => fixture('listings/with_documents.json'))
122
+ client.get("/listings/1234")
123
+ count.should eq(1)
124
+ refresh_count.should eq(1)
125
+ client.session.expired?.should eq(false)
126
+ end
127
+ end
128
+ context "and an invalid refresh token" do
129
+ it "should reset the session and reauthenticate" do
130
+ count = 0
131
+ stub_request(:post, provider.access_uri).
132
+ with(:body => '{"code":"my_code","client_secret":"example-password","client_id":"example-id","redirect_uri":"https://exampleapp.fbsdata.com/oauth-callback","grant_type":"authorization_code"}').
133
+ to_return do
134
+ count += 1
135
+ {:body => fixture("oauth2/access.json"), :status=>200}
136
+ end
137
+ # Make sure the auth request goes out twice.
138
+ # Fail the first time, but then return the correct value after reauthentication
139
+ stub_request(:get, "https://api.flexmls.com/#{FlexmlsApi.version}/listings/1234").
140
+ to_return(:body => fixture('errors/expired.json'), :status => 401).times(1).then.
141
+ to_return(:body => fixture('listings/with_documents.json'))
142
+
143
+ client.get("/listings/1234")
144
+ count.should eq(2)
145
+ client.session.expired?.should eq(false)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe FlexmlsApi::Authentication::BaseOAuth2Provider do
152
+ context "session_timeout" do
153
+ it "should provide a default" do
154
+ subject.session_timeout.should eq(86400)
155
+ end
156
+ describe TestOAuth2Provider do
157
+ subject { TestOAuth2Provider.new }
158
+ it "should be able to override the session timeout" do
159
+ subject.session_timeout.should eq(7200)
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "password authentication" do
166
+ let(:provider) { TestCLIOAuth2Provider.new() }
167
+ let(:client) { FlexmlsApi::Client.new({:authentication_mode => FlexmlsApi::Authentication::OAuth2,:oauth2_provider => provider}) }
168
+ subject {client.authenticator }
169
+ it "should authenticate the api credentials with username and password" do
170
+ stub_request(:post, provider.access_uri).
171
+ with(:body =>
172
+ '{"username":"example-user","client_secret":"example-secret","client_id":"example-id","password":"example-password","grant_type":"password"}'
173
+ ).
174
+ to_return(:body => fixture("oauth2/access.json"), :status=>200)
175
+ subject.authenticate.access_token.should eq("04u7h-4cc355-70k3n")
176
+ subject.authenticate.expires_in.should eq(60)
177
+ end
178
+ end
179
+ describe FlexmlsApi::Authentication::OAuth2Impl do
180
+ it "should load a provider" do
181
+ example = "FlexmlsApi::Authentication::OAuth2Impl::PasswordProvider"
182
+ FlexmlsApi::Authentication::OAuth2Impl.load_provider(example,{}).class.to_s.should eq(example)
183
+ prefix = "::#{example}"
184
+ FlexmlsApi::Authentication::OAuth2Impl.load_provider(prefix,{}).class.to_s.should eq(example)
185
+ bad_example = "Derp::Derp::Derp::DerpProvider"
186
+ expect{FlexmlsApi::Authentication::OAuth2Impl.load_provider(bad_example,{}).class.to_s.should eq(bad_example)}.to raise_error(ArgumentError)
187
+ end
188
+
189
+ end
190
+
191
+ describe FlexmlsApi::Authentication::OAuthSession do
192
+ it "should serialize to json" do
193
+ args = {
194
+ "access_token" => "abc",
195
+ "expires_in" => 3600,
196
+ "refresh_token" => "123",
197
+ "refresh_timeout" => 10000,
198
+ "start_time" => "2012-01-01T00:00:00+00:00"
199
+ }
200
+ session = FlexmlsApi::Authentication::OAuthSession.new(args)
201
+ session.start_time.should eq(DateTime.parse(args["start_time"]))
202
+ JSON.parse(session.to_json).should eq(args)
203
+
204
+ end
205
+ end