netprospex 0.0.1

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 (41) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +1 -0
  6. data/fixtures/cassettes/acl-error.yml +45 -0
  7. data/fixtures/cassettes/auth-error.yml +45 -0
  8. data/fixtures/cassettes/incomplete-organization.yml +55 -0
  9. data/fixtures/cassettes/incomplete-person.yml +52 -0
  10. data/fixtures/cassettes/industries.yml +136 -0
  11. data/fixtures/cassettes/pag-error.yml +45 -0
  12. data/fixtures/cassettes/person-by-email.yml +55 -0
  13. data/fixtures/cassettes/person-by-id.yml +55 -0
  14. data/fixtures/cassettes/person-list.yml +619 -0
  15. data/fixtures/cassettes/req-error.yml +45 -0
  16. data/lib/netprospex/api/address.rb +9 -0
  17. data/lib/netprospex/api/organization.rb +10 -0
  18. data/lib/netprospex/api/person.rb +10 -0
  19. data/lib/netprospex/api/phone.rb +9 -0
  20. data/lib/netprospex/api/sub_objects.rb +52 -0
  21. data/lib/netprospex/client.rb +58 -0
  22. data/lib/netprospex/configuration.rb +27 -0
  23. data/lib/netprospex/exceptions.rb +13 -0
  24. data/lib/netprospex/helpers/formatters.rb +67 -0
  25. data/lib/netprospex/middleware/raise_exceptions.rb +31 -0
  26. data/lib/netprospex/middleware/rubyize.rb +16 -0
  27. data/lib/netprospex/query_methods.rb +41 -0
  28. data/lib/netprospex/version.rb +3 -0
  29. data/lib/netprospex.rb +8 -0
  30. data/netprospex.gemspec +31 -0
  31. data/spec/netprospex/api/organization_spec.rb +52 -0
  32. data/spec/netprospex/api/person_spec.rb +46 -0
  33. data/spec/netprospex/client_spec.rb +27 -0
  34. data/spec/netprospex/configuration_spec.rb +22 -0
  35. data/spec/netprospex/helpers/formatters_spec.rb +78 -0
  36. data/spec/netprospex/middleware/raise_exceptions_spec.rb +83 -0
  37. data/spec/netprospex/query_methods_spec.rb +69 -0
  38. data/spec/netprospex_spec.rb +5 -0
  39. data/spec/spec_helper.rb +7 -0
  40. data/spec/vcr_helper.rb +12 -0
  41. metadata +257 -0
@@ -0,0 +1,45 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://api-sb.netprospex.com/1.1/person/list.json
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.7
12
+ Authorization:
13
+ - OAuth oauth_consumer_key="<NETPROSPEX_KEY>", oauth_nonce="2e2556d88815db3044d263110492c76d",
14
+ oauth_signature="MX5V81z3L50m1oIYkNYYn%2BgLT18%3D", oauth_signature_method="HMAC-SHA1",
15
+ oauth_timestamp="1366302031", oauth_token="<NETPROSPEX_USER_TOKEN>", oauth_version="1.0"
16
+ Accept-Encoding:
17
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
18
+ Accept:
19
+ - ! '*/*'
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ Date:
26
+ - Thu, 18 Apr 2013 16:20:32 GMT
27
+ Server:
28
+ - Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/1.0.0-fips DAV/2 mod_wsgi/3.3
29
+ Python/2.7.2 PHP/5.3.10
30
+ X-Powered-By:
31
+ - PHP/5.3.10
32
+ Connection:
33
+ - close
34
+ Content-Length:
35
+ - '484'
36
+ Content-Type:
37
+ - application/json
38
+ body:
39
+ encoding: US-ASCII
40
+ string: ! '{"response":{"version":"1.1","responseId":"CB93209F-A577-A249-8714-7849B42E325F","responseType":"error","request":{"startTime":"2013-04-18
41
+ 12:20:32","requestType":"person_list","url":"\/1.1\/person\/list.json","userId":"d5c72b79-a2ed-11e2-8c42-00259059ba92"},"transaction":{"accountId":"d5c51e5f-a2ed-11e2-8c42-00259059ba92","accountStartBalance":7120,"accountEndBalance":null},"error":{"message":"no
42
+ valid arguments were found","code":"REQ"},"debug":{"requestTime":0.0180189609528}}}'
43
+ http_version:
44
+ recorded_at: Thu, 18 Apr 2013 16:20:32 GMT
45
+ recorded_with: VCR 2.4.0
@@ -0,0 +1,9 @@
1
+ require 'ostruct'
2
+
3
+ module NetProspex::Api
4
+ class Address < OpenStruct
5
+ def to_s
6
+ self.formatted_address
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ require 'netprospex/api/sub_objects.rb'
2
+ require 'ostruct'
3
+
4
+ module NetProspex::Api
5
+ class Organization < OpenStruct
6
+ include HasAddress
7
+ include HasPhones
8
+ include HasDomains
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'netprospex/api/sub_objects.rb'
2
+ require 'ostruct'
3
+
4
+ module NetProspex::Api
5
+ class Person < OpenStruct
6
+ include HasAddress
7
+ include HasPhones
8
+ include HasOrganization
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ require 'ostruct'
2
+
3
+ module NetProspex::Api
4
+ class Phone < OpenStruct
5
+ def to_s
6
+ self.formatted_number
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ module NetProspex::Api
2
+ module HasPhones
3
+ def initialize(*args)
4
+ super(*args)
5
+ self.phones ||= []
6
+ self.phones.map! do |phone|
7
+ if phone.is_a? Hash
8
+ if phone[:national_number] && !phone[:national_number].empty?
9
+ NetProspex::Api::Phone.new(phone)
10
+ else
11
+ nil
12
+ end
13
+ else
14
+ phone
15
+ end
16
+ end
17
+ self.phones.compact!
18
+ end
19
+ end
20
+ module HasAddress
21
+ def initialize(*args)
22
+ super(*args)
23
+ if self.postal_address.is_a? Hash
24
+ formatted_address = self.postal_address[:formatted_address]
25
+ if formatted_address && !formatted_address.empty?
26
+ self.postal_address = NetProspex::Api::Address.new(self.postal_address)
27
+ else
28
+ self.postal_address = nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ module HasOrganization
34
+ def initialize(*args)
35
+ super(*args)
36
+ if self.organization.is_a? Hash
37
+ self.organization = NetProspex::Api::Organization.new(self.organization)
38
+ end
39
+ end
40
+ end
41
+ module HasDomains
42
+ def initialize(*args)
43
+ super(*args)
44
+ if self.domains.is_a? Hash
45
+ domain = self.domains.fetch(:domain, {})[:url]
46
+ self.domains = domain ? [domain] : []
47
+ elsif self.domains.is_a? Array #TODO: I'm just guessing at this formatting
48
+ self.domains.map! {|h| h[:domain][:url]}
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ require "netprospex/query_methods"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+
5
+ module NetProspex
6
+ class Client
7
+ include NetProspex::Helpers::Formatters
8
+ include NetProspex::QueryMethods
9
+ attr_reader :account_balance
10
+
11
+ def initialize(token, secret)
12
+ @token = token
13
+ @secret = secret
14
+ end
15
+
16
+ def get(path, query={})
17
+ http.get(api_url(path, query)).body
18
+ end
19
+
20
+ def post(path, params={})
21
+ http.post(api_url(path), params).body
22
+ end
23
+
24
+ protected
25
+
26
+ def api_base
27
+ @api_base ||= case NetProspex.environment
28
+ when :production
29
+ "http://api.netprospex.com/1.1"
30
+ when :sandbox
31
+ "http://api-sb.netprospex.com/1.1"
32
+ end
33
+ end
34
+
35
+ def api_url(path, query={})
36
+ api_base + path + stringify_query(query)
37
+ end
38
+
39
+ def http
40
+ raise NetProspex::ConfigurationError if api_base.nil?
41
+
42
+ @http ||= Faraday.new do |builder|
43
+ builder.use Faraday::Request::OAuth, {
44
+ consumer_key: NetProspex.consumer_key,
45
+ consumer_secret: NetProspex.consumer_secret,
46
+ token: @token,
47
+ token_secret: @secret
48
+ }
49
+
50
+ builder.use NetProspex::Middleware::RaiseExceptions
51
+ builder.use NetProspex::Middleware::Rubyize, content_type: /\bjson$/
52
+ builder.use FaradayMiddleware::ParseJson, content_type: /\bjson$/
53
+
54
+ builder.adapter :net_http
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,27 @@
1
+ module NetProspex
2
+ module Configuration
3
+ attr_accessor :consumer_key, :consumer_secret
4
+ attr_reader :environment
5
+ def environment=(env)
6
+ if [:production, :sandbox].include? env
7
+ @environment = env
8
+ else
9
+ raise NetProspex::ConfigurationError.new("Unknown environment")
10
+ end
11
+ end
12
+
13
+ def self.extended(base)
14
+ base.reset
15
+ end
16
+
17
+ def reset
18
+ self.consumer_key = nil
19
+ self.consumer_secret = nil
20
+ self.environment = :production
21
+ end
22
+
23
+ def configure
24
+ yield self
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module NetProspex
2
+ class Error < StandardError; end
3
+ class ConfigurationError < Error; end
4
+
5
+ class ApiError < Error; end
6
+ class AccessDenied < ApiError; end
7
+ class AuthenticationError < ApiError; end
8
+ class DatabaseError < ApiError; end
9
+ class SearchError < ApiError; end
10
+ class PaginationError < ApiError; end
11
+ class ArgumentMissing < ApiError; end
12
+ class ZeroBalanceError < ApiError; end
13
+ end
@@ -0,0 +1,67 @@
1
+ require "active_support/all"
2
+ require "uri"
3
+
4
+ module NetProspex
5
+ module Helpers
6
+ module Formatters
7
+ def stringify_query(query)
8
+ return "" if query.blank?
9
+ components = []
10
+ unrubyize_keys(query).each do |k,v|
11
+ if v.is_a? Array
12
+ v.each do |w|
13
+ components << [k+'[]', w]
14
+ end
15
+ else
16
+ components << [k,v]
17
+ end
18
+ end
19
+ "?" + URI.encode_www_form(components)
20
+ end
21
+
22
+ def unrubyize_keys(hash)
23
+ new_hash = {}
24
+ hash.each do |k,v|
25
+ new_k = case k
26
+ when Symbol
27
+ k.to_s.camelize(:lower)
28
+ else
29
+ k
30
+ end
31
+ new_v = case v
32
+ when Hash
33
+ unrubyize_keys(v)
34
+ when Array
35
+ v.map {|w| (Hash === w) ? unrubyize_keys(w) : w}
36
+ else
37
+ v
38
+ end
39
+ new_hash[new_k] = new_v
40
+ end
41
+ new_hash
42
+ end
43
+
44
+ def rubyize_keys(hash)
45
+ new_hash = {}
46
+ hash.each do |k,v|
47
+ new_k = case k
48
+ when String
49
+ k.underscore.to_sym
50
+ else
51
+ k
52
+ end
53
+ new_v = case v
54
+ when Hash
55
+ rubyize_keys(v)
56
+ when Array
57
+ v.map {|w| (Hash === w) ? rubyize_keys(w) : w}
58
+ else
59
+ v
60
+ end
61
+ new_hash[new_k] = new_v
62
+ end
63
+ new_hash
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module NetProspex
4
+ module Middleware
5
+ class RaiseExceptions < Faraday::Middleware
6
+ def call(environment)
7
+ @app.call(environment).on_complete do |env|
8
+ next unless env[:body].is_a?(Hash) && (error = env[:body][:response][:error])
9
+ case error[:code]
10
+ when "ACL"
11
+ raise NetProspex::AccessDenied.new(error[:message])
12
+ when "AUTH"
13
+ raise NetProspex::AuthenticationError.new(error[:message])
14
+ when "DB"
15
+ raise NetProspex::DatabaseError.new(error[:message])
16
+ when "SS"
17
+ raise NetProspex::SearchError.new(error[:message])
18
+ when "PAG"
19
+ raise NetProspex::PaginationError.new(error[:message])
20
+ when "REQ"
21
+ raise NetProspex::ArgumentMissing.new(error[:message])
22
+ when "ZBAL"
23
+ raise NetProspex::ZeroBalanceError.new(error[:message])
24
+ when "FAULT","UNX"
25
+ raise NetProspex::ApiError.new(error[:message])
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ require 'faraday_middleware/response_middleware'
2
+ module NetProspex
3
+ module Middleware
4
+ class Rubyize < FaradayMiddleware::ResponseMiddleware
5
+ extend NetProspex::Helpers::Formatters
6
+
7
+ define_parser do |body|
8
+ rubyize_keys(body) if body.is_a? Hash
9
+ end
10
+
11
+ def parse_response?(env)
12
+ env[:body].is_a? Hash
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ module NetProspex
2
+ module QueryMethods
3
+ def find_person(query)
4
+ response = get('/person/profile.json', query)
5
+ store_balance(response)
6
+ if p = response.fetch(:response,{}).fetch(:person_profile,{})[:person]
7
+ return NetProspex::Api::Person.new(p)
8
+ else
9
+ return nil
10
+ end
11
+ end
12
+
13
+ def find_person_by_id(id)
14
+ find_person(person_id: id)
15
+ end
16
+
17
+ def find_person_by_email(email)
18
+ find_person(email: email)
19
+ end
20
+
21
+ def find_people(query)
22
+ # these are boolean but the API wants 0 or 1
23
+ [:calc_found_rows, :preview].each do |k|
24
+ query[k] = 1 if query[k]
25
+ end
26
+ response = get('/person/list.json', query)
27
+ store_balance(response)
28
+ if persons = response.fetch(:response,{}).fetch(:person_list,{})[:persons]
29
+ return persons.map{|p| NetProspex::Api::Person.new(p)}
30
+ else
31
+ return [] #TODO should this raise an error?
32
+ end
33
+ end
34
+
35
+ protected
36
+ def store_balance(hash)
37
+ balance = hash.fetch(:response,{}).fetch(:transaction,{})[:account_end_balance]
38
+ @account_balance = balance if balance
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module NetProspex
2
+ VERSION = "0.0.1"
3
+ end
data/lib/netprospex.rb ADDED
@@ -0,0 +1,8 @@
1
+ Dir[File.dirname(__FILE__) + '/netprospex/helpers/*.rb'].each {|file| require file }
2
+ Dir[File.dirname(__FILE__) + '/netprospex/middleware/*.rb'].each {|file| require file }
3
+ Dir[File.dirname(__FILE__) + '/netprospex/api/*.rb'].each {|file| require file }
4
+ Dir[File.dirname(__FILE__) + '/netprospex/*.rb'].each {|file| require file }
5
+
6
+ module NetProspex
7
+ extend Configuration
8
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'netprospex/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'netprospex'
8
+ gem.version = NetProspex::VERSION
9
+ gem.authors = ['Michael Limiero']
10
+ gem.email = ['mike5713@gmail.com']
11
+ gem.homepage = 'https://github.com/SalesLoft/netprospex-ruby'
12
+ gem.summary = %q{Ruby bindings for NetProspex API}
13
+ gem.description = %q{The NetProspex API provides search and lookup for detailed information on people and companies.}
14
+ gem.license = 'MIT'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.add_development_dependency 'bundler', '~> 1.3'
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'rspec'
24
+ gem.add_development_dependency 'webmock'
25
+ gem.add_development_dependency 'vcr'
26
+ gem.add_runtime_dependency 'activesupport'
27
+ gem.add_runtime_dependency 'faraday'
28
+ gem.add_runtime_dependency 'faraday_middleware'
29
+ gem.add_runtime_dependency 'json'
30
+ gem.add_runtime_dependency 'simple_oauth'
31
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'vcr_helper'
3
+ require_relative '../../../lib/netprospex.rb'
4
+
5
+ describe NetProspex::Api::Organization do
6
+ before(:each) do
7
+ NetProspex.configure do |c|
8
+ c.consumer_key = ENV['NETPROSPEX_KEY']
9
+ c.consumer_secret = ENV['NETPROSPEX_SECRET']
10
+ c.environment = :sandbox
11
+ end
12
+ end
13
+
14
+ let(:client) { NetProspex::Client.new(ENV['NETPROSPEX_USER_TOKEN'], ENV['NETPROSPEX_USER_SECRET']) }
15
+ let(:person) {
16
+ VCR.use_cassette("person-by-id") do
17
+ client.find_person_by_id(29435533)
18
+ end
19
+ }
20
+ let(:organization) { person.organization }
21
+
22
+ it "simplifies domains to an array" do
23
+ organization.domains.should be_an Array
24
+ organization.domains.first.should be_a String
25
+ end
26
+
27
+ it "converts phones to objects" do
28
+ organization.phones.should be_an Array
29
+ organization.phones.first.should be_a NetProspex::Api::Phone
30
+ end
31
+
32
+ it "converts address to an object" do
33
+ organization.postal_address.should be_a NetProspex::Api::Address
34
+ end
35
+
36
+ context "when missing data" do
37
+ let(:incomplete_organization) {
38
+ VCR.use_cassette("incomplete-organization") do
39
+ client.find_person_by_id(85652828).organization
40
+ end
41
+ }
42
+
43
+ it "has nil address" do
44
+ incomplete_organization.postal_address.should be_nil
45
+ end
46
+
47
+ it "has empty phones array" do
48
+ incomplete_organization.phones.should be_an Array
49
+ incomplete_organization.phones.should be_empty
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'vcr_helper'
3
+ require_relative '../../../lib/netprospex.rb'
4
+
5
+ describe NetProspex::Api::Person do
6
+ before(:each) do
7
+ NetProspex.configure do |c|
8
+ c.consumer_key = ENV['NETPROSPEX_KEY']
9
+ c.consumer_secret = ENV['NETPROSPEX_SECRET']
10
+ c.environment = :sandbox
11
+ end
12
+ end
13
+
14
+ let(:client) { NetProspex::Client.new(ENV['NETPROSPEX_USER_TOKEN'], ENV['NETPROSPEX_USER_SECRET']) }
15
+ let(:person) {
16
+ VCR.use_cassette("person-by-id") do
17
+ client.find_person_by_id(29435533)
18
+ end
19
+ }
20
+
21
+ it "converts organization to an object" do
22
+ person.organization.should be_a NetProspex::Api::Organization
23
+ end
24
+
25
+ it "converts phones to objects" do
26
+ person.phones.should be_an Array
27
+ person.phones.first.should be_a NetProspex::Api::Phone
28
+ end
29
+
30
+ it "converts address to an object" do
31
+ person.postal_address.should be_a NetProspex::Api::Address
32
+ end
33
+
34
+ context "when missing data" do
35
+ let(:incomplete_person) {
36
+ VCR.use_cassette("incomplete-person") do
37
+ client.find_people(organization_name: "Pardot", preview: true, limit: 1).first
38
+ end
39
+ }
40
+
41
+ it "has empty phones array" do
42
+ incomplete_person.phones.should be_an Array
43
+ incomplete_person.phones.should be_empty
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+ require "vcr_helper"
3
+ require_relative "../../lib/netprospex.rb"
4
+
5
+ describe NetProspex::Client do
6
+ before(:each) do
7
+ NetProspex.configure do |c|
8
+ c.consumer_key = ENV['NETPROSPEX_KEY']
9
+ c.consumer_secret = ENV['NETPROSPEX_SECRET']
10
+ c.environment = :sandbox
11
+ end
12
+ end
13
+
14
+ let(:client) { NetProspex::Client.new(ENV['NETPROSPEX_USER_TOKEN'], ENV['NETPROSPEX_USER_SECRET']) }
15
+
16
+ it "makes GET requests" do
17
+ VCR.use_cassette("industries") do
18
+ client.get("/industries.json")
19
+ end
20
+ end
21
+
22
+ it "makes GET requests with fancy parameters" do
23
+ VCR.use_cassette("person-list") do
24
+ client.get("/person/list.json", organization_name: ["Pardot", "Silverpop"])
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+ require_relative "../../lib/netprospex.rb"
3
+
4
+ describe NetProspex::Configuration do
5
+ it "can be configured" do
6
+ expect {
7
+ NetProspex.configure do |config|
8
+ config.consumer_key = "foo"
9
+ config.consumer_secret = "bar"
10
+ config.environment = :sandbox
11
+ end
12
+ }.to_not raise_exception
13
+ end
14
+
15
+ it "validates environment" do
16
+ expect {
17
+ NetProspex.configure do |config|
18
+ config.environment = :bad_value
19
+ end
20
+ }.to raise_exception(NetProspex::ConfigurationError)
21
+ end
22
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/netprospex.rb"
3
+
4
+ describe NetProspex::Helpers::Formatters do
5
+ include NetProspex::Helpers::Formatters
6
+ describe ".unrubyize_keys" do
7
+ it "converts snake_case to camelCase" do
8
+ unrubyize_keys(first_name: "John", last_name: "Doe").should == {
9
+ 'firstName' => 'John',
10
+ 'lastName' => 'Doe'
11
+ }
12
+ end
13
+
14
+ it "handles nested hashes" do
15
+ unrubyize_keys(some_person: {first_name: "John", last_name: "Doe"}).should ==
16
+ {'somePerson' => {'firstName' => 'John', 'lastName' => 'Doe'}}
17
+ end
18
+
19
+ it "handles array values" do
20
+ unrubyize_keys(some_people: [
21
+ {first_name: "John", last_name: "Doe"},
22
+ {first_name: "Mary", last_name: "Smith"}
23
+ ]).should == {'somePeople' => [
24
+ {'firstName' => 'John', 'lastName' => 'Doe'},
25
+ {'firstName' => 'Mary', 'lastName' => 'Smith'}
26
+ ]}
27
+ end
28
+ end
29
+
30
+ describe ".rubyize_keys" do
31
+ it "converts camelCase to snake_case" do
32
+ rubyize_keys({
33
+ 'firstName' => 'John',
34
+ 'lastName' => 'Doe'
35
+ }).should == {first_name: "John", last_name: "Doe"}
36
+ end
37
+
38
+ it "handles nested hashes" do
39
+ rubyize_keys({'somePerson' =>
40
+ {'firstName' => 'John', 'lastName' => 'Doe'}}).should ==
41
+ {some_person: {first_name: "John", last_name: "Doe"}}
42
+ end
43
+
44
+ it "handles array values" do
45
+ rubyize_keys({'somePeople' => [
46
+ {'firstName' => 'John', 'lastName' => 'Doe'},
47
+ {'firstName' => 'Mary', 'lastName' => 'Smith'}
48
+ ]}).should == {some_people: [
49
+ {first_name: "John", last_name: "Doe"},
50
+ {first_name: "Mary", last_name: "Smith"}
51
+ ]}
52
+ end
53
+ end
54
+
55
+ describe ".stringify_query" do
56
+ it "is blank for empty query" do
57
+ stringify_query(nil).should == ""
58
+ stringify_query({}).should == ""
59
+ end
60
+
61
+ it "converts snake_case keys to camelCase" do
62
+ stringify_query(first_name: "John", last_name: "Doe").should ==
63
+ "?firstName=John&lastName=Doe"
64
+ end
65
+
66
+ it "escapes keys and values" do
67
+ stringify_query('be safe?&=' => 'foo').should ==
68
+ "?be+safe%3F%26%3D=foo"
69
+ stringify_query(first_name: "John&doEvil=true").should ==
70
+ "?firstName=John%26doEvil%3Dtrue"
71
+ end
72
+
73
+ it "handles array values" do
74
+ stringify_query(companies: ["Apple", "Google"]).should ==
75
+ "?companies%5B%5D=Apple&companies%5B%5D=Google"
76
+ end
77
+ end
78
+ end