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.
- data/History.txt +139 -0
- data/LICENSE +14 -0
- data/README.md +153 -0
- data/Rakefile +18 -0
- data/VERSION +1 -0
- data/bin/spark_api +8 -0
- data/bin/spark_api~ +8 -0
- data/lib/spark_api.rb +46 -0
- data/lib/spark_api/authentication.rb +55 -0
- data/lib/spark_api/authentication/api_auth.rb +104 -0
- data/lib/spark_api/authentication/api_auth.rb~ +104 -0
- data/lib/spark_api/authentication/base_auth.rb +47 -0
- data/lib/spark_api/authentication/base_auth.rb~ +47 -0
- data/lib/spark_api/authentication/oauth2.rb +198 -0
- data/lib/spark_api/authentication/oauth2.rb~ +199 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb +87 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_base.rb~ +87 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb +48 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_code.rb~ +49 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb +44 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_password.rb~ +45 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb +35 -0
- data/lib/spark_api/authentication/oauth2_impl/grant_type_refresh.rb~ +36 -0
- data/lib/spark_api/authentication/oauth2_impl/middleware.rb +38 -0
- data/lib/spark_api/authentication/oauth2_impl/middleware.rb~ +39 -0
- data/lib/spark_api/authentication/oauth2_impl/password_provider.rb +24 -0
- data/lib/spark_api/authentication/oauth2_impl/password_provider.rb~ +25 -0
- data/lib/spark_api/cli.rb +158 -0
- data/lib/spark_api/cli.rb~ +158 -0
- data/lib/spark_api/cli/api_auth.rb +8 -0
- data/lib/spark_api/cli/api_auth.rb~ +8 -0
- data/lib/spark_api/cli/oauth2.rb +14 -0
- data/lib/spark_api/cli/oauth2.rb~ +14 -0
- data/lib/spark_api/cli/setup.rb +47 -0
- data/lib/spark_api/cli/setup.rb~ +47 -0
- data/lib/spark_api/client.rb +27 -0
- data/lib/spark_api/configuration.rb +54 -0
- data/lib/spark_api/configuration.rb~ +54 -0
- data/lib/spark_api/configuration/yaml.rb +101 -0
- data/lib/spark_api/configuration/yaml.rb~ +101 -0
- data/lib/spark_api/connection.rb +42 -0
- data/lib/spark_api/faraday.rb +64 -0
- data/lib/spark_api/faraday.rb~ +64 -0
- data/lib/spark_api/models.rb +33 -0
- data/lib/spark_api/models.rb~ +33 -0
- data/lib/spark_api/models/account.rb +115 -0
- data/lib/spark_api/models/account.rb~ +115 -0
- data/lib/spark_api/models/base.rb +118 -0
- data/lib/spark_api/models/base.rb~ +118 -0
- data/lib/spark_api/models/connect_prefs.rb +10 -0
- data/lib/spark_api/models/connect_prefs.rb~ +10 -0
- data/lib/spark_api/models/constraint.rb +16 -0
- data/lib/spark_api/models/constraint.rb~ +16 -0
- data/lib/spark_api/models/contact.rb +49 -0
- data/lib/spark_api/models/contact.rb~ +49 -0
- data/lib/spark_api/models/custom_fields.rb +12 -0
- data/lib/spark_api/models/custom_fields.rb~ +12 -0
- data/lib/spark_api/models/document.rb +11 -0
- data/lib/spark_api/models/document.rb~ +11 -0
- data/lib/spark_api/models/finders.rb +45 -0
- data/lib/spark_api/models/finders.rb~ +45 -0
- data/lib/spark_api/models/idx_link.rb +47 -0
- data/lib/spark_api/models/idx_link.rb~ +47 -0
- data/lib/spark_api/models/listing.rb +197 -0
- data/lib/spark_api/models/listing.rb~ +197 -0
- data/lib/spark_api/models/listing_cart.rb +72 -0
- data/lib/spark_api/models/listing_cart.rb~ +72 -0
- data/lib/spark_api/models/market_statistics.rb +33 -0
- data/lib/spark_api/models/market_statistics.rb~ +33 -0
- data/lib/spark_api/models/message.rb +21 -0
- data/lib/spark_api/models/message.rb~ +21 -0
- data/lib/spark_api/models/note.rb +41 -0
- data/lib/spark_api/models/note.rb~ +41 -0
- data/lib/spark_api/models/notification.rb +42 -0
- data/lib/spark_api/models/notification.rb~ +42 -0
- data/lib/spark_api/models/open_house.rb +24 -0
- data/lib/spark_api/models/open_house.rb~ +24 -0
- data/lib/spark_api/models/photo.rb +70 -0
- data/lib/spark_api/models/photo.rb~ +70 -0
- data/lib/spark_api/models/property_types.rb +7 -0
- data/lib/spark_api/models/property_types.rb~ +7 -0
- data/lib/spark_api/models/saved_search.rb +16 -0
- data/lib/spark_api/models/saved_search.rb~ +16 -0
- data/lib/spark_api/models/shared_listing.rb +35 -0
- data/lib/spark_api/models/shared_listing.rb~ +35 -0
- data/lib/spark_api/models/standard_fields.rb +50 -0
- data/lib/spark_api/models/standard_fields.rb~ +50 -0
- data/lib/spark_api/models/subresource.rb +19 -0
- data/lib/spark_api/models/subresource.rb~ +19 -0
- data/lib/spark_api/models/system_info.rb +14 -0
- data/lib/spark_api/models/system_info.rb~ +14 -0
- data/lib/spark_api/models/tour_of_home.rb +24 -0
- data/lib/spark_api/models/tour_of_home.rb~ +24 -0
- data/lib/spark_api/models/video.rb +16 -0
- data/lib/spark_api/models/video.rb~ +16 -0
- data/lib/spark_api/models/virtual_tour.rb +18 -0
- data/lib/spark_api/models/virtual_tour.rb~ +18 -0
- data/lib/spark_api/multi_client.rb +59 -0
- data/lib/spark_api/multi_client.rb~ +59 -0
- data/lib/spark_api/paginate.rb +109 -0
- data/lib/spark_api/paginate.rb~ +109 -0
- data/lib/spark_api/primary_array.rb +29 -0
- data/lib/spark_api/primary_array.rb~ +29 -0
- data/lib/spark_api/request.rb +96 -0
- data/lib/spark_api/request.rb~ +96 -0
- data/lib/spark_api/response.rb +70 -0
- data/lib/spark_api/response.rb~ +70 -0
- data/lib/spark_api/version.rb +4 -0
- data/lib/spark_api/version.rb~ +4 -0
- data/script/console +6 -0
- data/script/console~ +6 -0
- data/script/example.rb +27 -0
- data/script/example.rb~ +27 -0
- data/spec/fixtures/accounts/all.json +160 -0
- data/spec/fixtures/accounts/my.json +74 -0
- data/spec/fixtures/accounts/my_portal.json +20 -0
- data/spec/fixtures/accounts/my_put.json +5 -0
- data/spec/fixtures/accounts/my_save.json +5 -0
- data/spec/fixtures/accounts/office.json +142 -0
- data/spec/fixtures/accounts/password_save.json +6 -0
- data/spec/fixtures/authentication_failure.json +7 -0
- data/spec/fixtures/base.json +13 -0
- data/spec/fixtures/contacts/contacts.json +28 -0
- data/spec/fixtures/contacts/my.json +19 -0
- data/spec/fixtures/contacts/new.json +11 -0
- data/spec/fixtures/contacts/new_empty.json +8 -0
- data/spec/fixtures/contacts/new_notify.json +11 -0
- data/spec/fixtures/contacts/post.json +10 -0
- data/spec/fixtures/contacts/tags.json +11 -0
- data/spec/fixtures/count.json +10 -0
- data/spec/fixtures/empty.json +3 -0
- data/spec/fixtures/errors/expired.json +7 -0
- data/spec/fixtures/errors/failure.json +5 -0
- data/spec/fixtures/errors/failure_with_constraint.json +17 -0
- data/spec/fixtures/errors/failure_with_msg.json +7 -0
- data/spec/fixtures/generic_delete.json +1 -0
- data/spec/fixtures/generic_failure.json +5 -0
- data/spec/fixtures/listing_carts/add_listing.json +13 -0
- data/spec/fixtures/listing_carts/add_listing_post.json +5 -0
- data/spec/fixtures/listing_carts/empty.json +5 -0
- data/spec/fixtures/listing_carts/listing_cart.json +19 -0
- data/spec/fixtures/listing_carts/new.json +12 -0
- data/spec/fixtures/listing_carts/post.json +10 -0
- data/spec/fixtures/listing_carts/remove_listing.json +13 -0
- data/spec/fixtures/listings/constraints.json +18 -0
- data/spec/fixtures/listings/constraints_with_pagination.json +24 -0
- data/spec/fixtures/listings/document_index.json +19 -0
- data/spec/fixtures/listings/multiple.json +69 -0
- data/spec/fixtures/listings/no_subresources.json +38 -0
- data/spec/fixtures/listings/open_houses.json +21 -0
- data/spec/fixtures/listings/photos/index.json +469 -0
- data/spec/fixtures/listings/photos/new.json +12 -0
- data/spec/fixtures/listings/photos/post.json +20 -0
- data/spec/fixtures/listings/put.json +5 -0
- data/spec/fixtures/listings/put_expiration_date.json +5 -0
- data/spec/fixtures/listings/saved_search.json +17 -0
- data/spec/fixtures/listings/shared_listing_get.json +14 -0
- data/spec/fixtures/listings/shared_listing_new.json +9 -0
- data/spec/fixtures/listings/shared_listing_post.json +10 -0
- data/spec/fixtures/listings/tour_of_homes.json +23 -0
- data/spec/fixtures/listings/videos_index.json +18 -0
- data/spec/fixtures/listings/virtual_tours_index.json +42 -0
- data/spec/fixtures/listings/with_documents.json +52 -0
- data/spec/fixtures/listings/with_permissions.json +44 -0
- data/spec/fixtures/listings/with_photos.json +110 -0
- data/spec/fixtures/listings/with_supplement.json +39 -0
- data/spec/fixtures/listings/with_videos.json +54 -0
- data/spec/fixtures/listings/with_vtour.json +48 -0
- data/spec/fixtures/logo_fbs.png +0 -0
- data/spec/fixtures/messages/new.json +14 -0
- data/spec/fixtures/messages/new_empty.json +7 -0
- data/spec/fixtures/messages/new_with_recipients.json +15 -0
- data/spec/fixtures/messages/post.json +5 -0
- data/spec/fixtures/notes/add.json +11 -0
- data/spec/fixtures/notes/agent_shared.json +11 -0
- data/spec/fixtures/notes/agent_shared_empty.json +7 -0
- data/spec/fixtures/notes/new.json +5 -0
- data/spec/fixtures/notifications/mark_read.json +1 -0
- data/spec/fixtures/notifications/new.json +8 -0
- data/spec/fixtures/notifications/new_empty.json +7 -0
- data/spec/fixtures/notifications/notifications.json +43 -0
- data/spec/fixtures/notifications/post.json +10 -0
- data/spec/fixtures/notifications/unread.json +10 -0
- data/spec/fixtures/oauth2/access.json +3 -0
- data/spec/fixtures/oauth2/access_with_old_refresh.json +5 -0
- data/spec/fixtures/oauth2/access_with_refresh.json +5 -0
- data/spec/fixtures/oauth2/authorization_code_body.json +7 -0
- data/spec/fixtures/oauth2/error.json +3 -0
- data/spec/fixtures/oauth2/password_body.json +7 -0
- data/spec/fixtures/oauth2/refresh_body.json +7 -0
- data/spec/fixtures/oauth2_error.json +3 -0
- data/spec/fixtures/property_types/property_types.json +31 -0
- data/spec/fixtures/session.json +10 -0
- data/spec/fixtures/standardfields/city.json +1031 -0
- data/spec/fixtures/standardfields/nearby.json +53 -0
- data/spec/fixtures/standardfields/standardfields.json +188 -0
- data/spec/fixtures/standardfields/stateorprovince.json +36 -0
- data/spec/fixtures/success.json +5 -0
- data/spec/json_helper.rb +76 -0
- data/spec/mock_helper.rb +124 -0
- data/spec/oauth2_helper.rb +68 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/unit/flexmls_api_spec.rb~ +23 -0
- data/spec/unit/spark_api/authentication/api_auth_spec.rb +169 -0
- data/spec/unit/spark_api/authentication/api_auth_spec.rb~ +169 -0
- data/spec/unit/spark_api/authentication/base_auth_spec.rb +10 -0
- data/spec/unit/spark_api/authentication/base_auth_spec.rb~ +10 -0
- data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb +10 -0
- data/spec/unit/spark_api/authentication/oauth2_impl/grant_type_base_spec.rb~ +10 -0
- data/spec/unit/spark_api/authentication/oauth2_spec.rb +205 -0
- data/spec/unit/spark_api/authentication/oauth2_spec.rb~ +205 -0
- data/spec/unit/spark_api/authentication_spec.rb +38 -0
- data/spec/unit/spark_api/authentication_spec.rb~ +38 -0
- data/spec/unit/spark_api/configuration/yaml_spec.rb +72 -0
- data/spec/unit/spark_api/configuration/yaml_spec.rb~ +72 -0
- data/spec/unit/spark_api/configuration_spec.rb +122 -0
- data/spec/unit/spark_api/configuration_spec.rb~ +122 -0
- data/spec/unit/spark_api/faraday_spec.rb +90 -0
- data/spec/unit/spark_api/faraday_spec.rb~ +90 -0
- data/spec/unit/spark_api/models/account_spec.rb +176 -0
- data/spec/unit/spark_api/models/base_spec.rb +106 -0
- data/spec/unit/spark_api/models/connect_prefs_spec.rb +9 -0
- data/spec/unit/spark_api/models/constraint_spec.rb +19 -0
- data/spec/unit/spark_api/models/contact_spec.rb +108 -0
- data/spec/unit/spark_api/models/contact_spec.rb~ +108 -0
- data/spec/unit/spark_api/models/document_spec.rb +32 -0
- data/spec/unit/spark_api/models/listing_cart_spec.rb +127 -0
- data/spec/unit/spark_api/models/listing_cart_spec.rb~ +127 -0
- data/spec/unit/spark_api/models/listing_spec.rb +320 -0
- data/spec/unit/spark_api/models/listing_spec.rb~ +320 -0
- data/spec/unit/spark_api/models/message_spec.rb +47 -0
- data/spec/unit/spark_api/models/message_spec.rb~ +47 -0
- data/spec/unit/spark_api/models/note_spec.rb +63 -0
- data/spec/unit/spark_api/models/note_spec.rb~ +63 -0
- data/spec/unit/spark_api/models/notification_spec.rb +62 -0
- data/spec/unit/spark_api/models/notification_spec.rb~ +62 -0
- data/spec/unit/spark_api/models/open_house_spec.rb +39 -0
- data/spec/unit/spark_api/models/photo_spec.rb +92 -0
- data/spec/unit/spark_api/models/property_types_spec.rb +33 -0
- data/spec/unit/spark_api/models/saved_search_spec.rb +40 -0
- data/spec/unit/spark_api/models/shared_listing_spec.rb +45 -0
- data/spec/unit/spark_api/models/shared_listing_spec.rb~ +45 -0
- data/spec/unit/spark_api/models/standard_fields_spec.rb +60 -0
- data/spec/unit/spark_api/models/system_info_spec.rb +83 -0
- data/spec/unit/spark_api/models/tour_of_home_spec.rb +44 -0
- data/spec/unit/spark_api/models/video_spec.rb +36 -0
- data/spec/unit/spark_api/models/virtual_tour_spec.rb +44 -0
- data/spec/unit/spark_api/multi_client_spec.rb +56 -0
- data/spec/unit/spark_api/multi_client_spec.rb~ +56 -0
- data/spec/unit/spark_api/paginate_spec.rb +224 -0
- data/spec/unit/spark_api/paginate_spec.rb~ +224 -0
- data/spec/unit/spark_api/primary_array_spec.rb +41 -0
- data/spec/unit/spark_api/primary_array_spec.rb~ +41 -0
- data/spec/unit/spark_api/request_spec.rb +344 -0
- data/spec/unit/spark_api/request_spec.rb~ +344 -0
- data/spec/unit/spark_api_spec.rb +23 -0
- 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
|