change-ruby 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/lib/change-ruby.rb +21 -0
- data/lib/exceptions/change_exception.rb +18 -0
- data/lib/requests/client.rb +71 -0
- data/lib/requests/const.rb +4 -0
- data/lib/resources/collection_resource.rb +31 -0
- data/lib/resources/member_resource.rb +73 -0
- data/lib/resources/organization.rb +13 -0
- data/lib/resources/petition.rb +19 -0
- data/lib/resources/petition_collection.rb +5 -0
- data/lib/resources/reason_collection.rb +5 -0
- data/lib/resources/resource.rb +83 -0
- data/lib/resources/signature_collection.rb +19 -0
- data/lib/resources/target_collection.rb +5 -0
- data/lib/resources/update_collection.rb +5 -0
- data/lib/resources/user.rb +16 -0
- data/test/client_test.rb +79 -0
- data/test/collection_resource_test.rb +60 -0
- data/test/member_resource_test.rb +208 -0
- data/test/signature_collection_resource_test.rb +83 -0
- data/test/util/test_helper.rb +3 -0
- metadata +113 -0
data/lib/change-ruby.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# $LOAD_PATH << './lib' # Uncomment to load change-ruby in console without installing gem.
|
2
|
+
|
3
|
+
require 'digest/sha2'
|
4
|
+
require 'httparty'
|
5
|
+
|
6
|
+
require 'exceptions/change_exception'
|
7
|
+
|
8
|
+
require 'requests/const'
|
9
|
+
require 'requests/client'
|
10
|
+
|
11
|
+
require 'resources/resource'
|
12
|
+
require 'resources/collection_resource'
|
13
|
+
require 'resources/member_resource'
|
14
|
+
require 'resources/organization'
|
15
|
+
require 'resources/user'
|
16
|
+
require 'resources/petition'
|
17
|
+
require 'resources/petition_collection'
|
18
|
+
require 'resources/signature_collection'
|
19
|
+
require 'resources/target_collection'
|
20
|
+
require 'resources/reason_collection'
|
21
|
+
require 'resources/update_collection'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Change
|
2
|
+
module Exceptions
|
3
|
+
class ChangeException < StandardError
|
4
|
+
|
5
|
+
attr_reader :code
|
6
|
+
attr_reader :messages
|
7
|
+
|
8
|
+
def initialize(messages, code = nil)
|
9
|
+
@code = code
|
10
|
+
@messages = messages
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
@messages.first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Change
|
2
|
+
module Requests
|
3
|
+
class Client
|
4
|
+
include HTTParty
|
5
|
+
include Change::Exceptions
|
6
|
+
|
7
|
+
def initialize(properties = {})
|
8
|
+
@api_key = properties.delete(:api_key)
|
9
|
+
@secret_token = properties.delete(:secret_token)
|
10
|
+
raise "An API key must be specified." if @api_key.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def request(request_on, request_type, resource, params)
|
14
|
+
method = request_type.delete(:method)
|
15
|
+
action_or_collection = request_type.delete(:action) || request_type.delete(:collection)
|
16
|
+
endpoint = resource.endpoint(request_on, action_or_collection)
|
17
|
+
|
18
|
+
params[:api_key] = @api_key
|
19
|
+
|
20
|
+
if resource.needs_authorization?(method)
|
21
|
+
params[:endpoint] = endpoint
|
22
|
+
params[:timestamp] = Time.now.utc.iso8601
|
23
|
+
|
24
|
+
auth_key_to_use = params.delete(:auth_key_to_use)
|
25
|
+
params[:rsig] = generate_rsig(params, auth_key_to_use['auth_key'])
|
26
|
+
end
|
27
|
+
|
28
|
+
response = send(method.to_s, final_url(endpoint), params)
|
29
|
+
deal_with_response(response)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def deal_with_response(response)
|
35
|
+
case response.code
|
36
|
+
when 200, 202
|
37
|
+
response.parsed_response
|
38
|
+
else
|
39
|
+
messages = response.parsed_response['messages']
|
40
|
+
raise ChangeException.new(messages, response.code), messages.join(' '), caller
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def base_url
|
45
|
+
"https://#{Change::HOST}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def final_url(endpoint)
|
49
|
+
base_url + endpoint
|
50
|
+
end
|
51
|
+
|
52
|
+
def get(url, params)
|
53
|
+
HTTParty.get(url, { :query => params })
|
54
|
+
end
|
55
|
+
|
56
|
+
def post(url, params)
|
57
|
+
HTTParty.post(url, { :body => params })
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate_rsig(params, auth_key)
|
61
|
+
body_to_digest = "#{post_body(params)}#{@secret_token}#{auth_key}"
|
62
|
+
Digest::SHA2.hexdigest(body_to_digest)
|
63
|
+
end
|
64
|
+
|
65
|
+
def post_body(params)
|
66
|
+
HTTParty::HashConversions.to_params(params)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Change
|
2
|
+
module Resources
|
3
|
+
class CollectionResource < Resource
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# Overridden for special pluralizations
|
8
|
+
def collection_name
|
9
|
+
name = self.name.split('::').last.downcase
|
10
|
+
name = name.match(/(.+)collection/)[1]
|
11
|
+
"#{name}s"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :parent_resource
|
17
|
+
attr_accessor :collection
|
18
|
+
|
19
|
+
def initialize(parent_resource, collection = nil, properties = {})
|
20
|
+
@parent_resource = parent_resource
|
21
|
+
@collection = collection unless collection.nil?
|
22
|
+
super(@parent_resource.client, properties)
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(params)
|
26
|
+
@parent_resource.load_collection(self.class.collection_name.to_sym, params)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Change
|
2
|
+
module Resources
|
3
|
+
class MemberResource < Resource
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# This is the Change.org name for the resource type. It
|
8
|
+
# is automatically derived from the class name.
|
9
|
+
#
|
10
|
+
# @return [String] the name of the resource
|
11
|
+
def member_name
|
12
|
+
self.name.split('::').last.downcase
|
13
|
+
end
|
14
|
+
|
15
|
+
# This is the Change.org pluralized name for the resource type. While it
|
16
|
+
# can be overridden in sub-classes for non-standard English
|
17
|
+
# pluralizations, it is automatically derived from the class name.
|
18
|
+
#
|
19
|
+
# @return [String] the pluralized name of the resource
|
20
|
+
def collection_name
|
21
|
+
"#{self.name.split('::').last.downcase}s"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
# The unique Change.org ID of the resource.
|
27
|
+
attr_accessor :id
|
28
|
+
|
29
|
+
# The fields on the resource. The Change.org API documentation has the
|
30
|
+
# full list of fields that may be returned for each resource.
|
31
|
+
attr_accessor :properties
|
32
|
+
|
33
|
+
def initialize(client, properties = {})
|
34
|
+
@id = properties.delete(:id) || properties.delete("#{self.class.member_name}_id")
|
35
|
+
super(client, properties)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Shared resource requests
|
39
|
+
|
40
|
+
# Retrieves the unique Change.org ID for the current resource by its
|
41
|
+
# resource current URL.
|
42
|
+
#
|
43
|
+
# @param resource_url [String] the current Change.org URL of the resource
|
44
|
+
# @return [Integer] the unique Change.org ID of the resource
|
45
|
+
def get_id(resource_url)
|
46
|
+
response = make_request(:collection, { :method => :get, :action => :get_id }, { "#{self.class.member_name}_url".to_sym => resource_url })
|
47
|
+
response["#{self.class.member_name}_id"]
|
48
|
+
end
|
49
|
+
|
50
|
+
def load(resource_id_or_url = nil, params = {})
|
51
|
+
if resource_id_or_url.is_a?(Integer)
|
52
|
+
@id = resource_id_or_url
|
53
|
+
elsif resource_id_or_url.is_a?(String)
|
54
|
+
@id = get_id(resource_id_or_url)
|
55
|
+
end
|
56
|
+
raise "Missing resource ID." if @id.nil?
|
57
|
+
response = make_request(:member, { :method => :get }, params)
|
58
|
+
response.delete("#{self.class.member_name}_id")
|
59
|
+
@properties = response
|
60
|
+
end
|
61
|
+
|
62
|
+
def load_collection(collection, params = {})
|
63
|
+
response = make_request(:member, { :method => :get, :collection => collection }, params)
|
64
|
+
if response.is_a?(Array)
|
65
|
+
self.send(collection).collection = response
|
66
|
+
else
|
67
|
+
self.send(collection).collection = response[collection.to_s]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Change
|
2
|
+
module Resources
|
3
|
+
class Petition < MemberResource
|
4
|
+
|
5
|
+
attr_accessor :signatures
|
6
|
+
attr_accessor :targets
|
7
|
+
attr_accessor :reasons
|
8
|
+
attr_accessor :updates
|
9
|
+
|
10
|
+
def initialize(client, properties = {})
|
11
|
+
super(client, properties)
|
12
|
+
@signatures = SignatureCollection.new(self)
|
13
|
+
@targets = TargetCollection.new(self)
|
14
|
+
@reasons = ReasonCollection.new(self)
|
15
|
+
@updates = UpdateCollection.new(self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Change
|
2
|
+
module Resources
|
3
|
+
class Resource
|
4
|
+
|
5
|
+
attr_accessor :client
|
6
|
+
|
7
|
+
def initialize(client, properties = {})
|
8
|
+
@client = client
|
9
|
+
@auth_keys = []
|
10
|
+
|
11
|
+
initial_auth_key = properties.delete(:auth_key)
|
12
|
+
add_new_auth_key(initial_auth_key) unless initial_auth_key.nil?
|
13
|
+
@properties = properties
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_request(request_on, request_type, params = {})
|
17
|
+
@client.request(request_on, request_type, self, params)
|
18
|
+
end
|
19
|
+
|
20
|
+
def endpoint(request_on, action_or_collection = nil)
|
21
|
+
path_parts = send("#{request_on.to_s}_path", action_or_collection)
|
22
|
+
path = path_parts.join('/')
|
23
|
+
path.prepend('/')
|
24
|
+
end
|
25
|
+
|
26
|
+
def needs_authorization?(method)
|
27
|
+
method != :get
|
28
|
+
end
|
29
|
+
|
30
|
+
def auth_key(key_number = 0)
|
31
|
+
@auth_keys[key_number]
|
32
|
+
end
|
33
|
+
|
34
|
+
def auth_key=(new_key)
|
35
|
+
add_new_auth_key(new_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Shared resource requests
|
39
|
+
|
40
|
+
def request_auth_key(params)
|
41
|
+
response = make_request(:member, { :method => :post, :collection => :auth_keys }, params)
|
42
|
+
if response['status'] == 'granted'
|
43
|
+
add_new_auth_key(response)
|
44
|
+
true
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def add_new_auth_key(key_object)
|
53
|
+
key_object = { :auth_key => key_object } if key_object.is_a?(String)
|
54
|
+
key_object.delete('status')
|
55
|
+
key_object.delete('result')
|
56
|
+
key_object.delete("#{self.class.member_name}_id")
|
57
|
+
@auth_keys << key_object unless @auth_keys.include?(key_object)
|
58
|
+
end
|
59
|
+
|
60
|
+
def member_path(action_or_collection = nil)
|
61
|
+
raise "Can't generate a member path without an ID." if @id.nil?
|
62
|
+
path_parts = collection_path
|
63
|
+
path_parts << @id
|
64
|
+
path_parts << action_or_collection if action_or_collection
|
65
|
+
path_parts
|
66
|
+
end
|
67
|
+
|
68
|
+
def collection_path(action = nil)
|
69
|
+
path_parts = [Change::VERSION]
|
70
|
+
if @parent_resource
|
71
|
+
path_parts << @parent_resource.class.collection_name
|
72
|
+
path_parts << @parent_resource.id
|
73
|
+
path_parts << self.class.collection_name
|
74
|
+
else
|
75
|
+
path_parts << self.class.collection_name
|
76
|
+
path_parts << action if action
|
77
|
+
end
|
78
|
+
path_parts
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Change
|
2
|
+
module Resources
|
3
|
+
class SignatureCollection < CollectionResource
|
4
|
+
|
5
|
+
def auth_key
|
6
|
+
@parent_resource.auth_key
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_signature(params = {}, auth_key_to_use = nil)
|
10
|
+
auth_key_to_use ||= auth_key
|
11
|
+
raise "No auth key specified." if auth_key_to_use.nil?
|
12
|
+
params[:auth_key_to_use] = auth_key_to_use
|
13
|
+
params[:source] = auth_key_to_use['source']
|
14
|
+
response = make_request(:collection, { :method => :post }, params)
|
15
|
+
response['result'] == 'success'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Change
|
2
|
+
module Resources
|
3
|
+
class User < MemberResource
|
4
|
+
|
5
|
+
attr_accessor :petitions
|
6
|
+
attr_accessor :signatures_on_petitions
|
7
|
+
|
8
|
+
def initialize(client, properties = {})
|
9
|
+
super(client, properties)
|
10
|
+
@petitions = PetitionCollection.new(self)
|
11
|
+
@signatures_on_petitions = PetitionCollection.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'util/test_helper'
|
2
|
+
|
3
|
+
describe 'Client' do
|
4
|
+
|
5
|
+
MockResponse = Struct.new("Response", :code, :parsed_response)
|
6
|
+
|
7
|
+
before do
|
8
|
+
@api_key_example = 'goodbye_world'
|
9
|
+
@secret_token_example = 'hello_world'
|
10
|
+
@client = Change::Requests::Client.new({
|
11
|
+
:api_key => @api_key_example,
|
12
|
+
:secret_token => @secret_token_example
|
13
|
+
})
|
14
|
+
@petition = Change::Resources::Petition.new(@client)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#request' do
|
18
|
+
|
19
|
+
it "should load a resource with a get request" do
|
20
|
+
url_to_be_called = 'https://api.change.org/v1/petitions/1'
|
21
|
+
params = { :api_key => @api_key_example }
|
22
|
+
@client.expects(:send)
|
23
|
+
.with('get', url_to_be_called, params)
|
24
|
+
.once
|
25
|
+
.returns(MockResponse.new(200, { "name" => "hey world" }))
|
26
|
+
@petition.load(1)
|
27
|
+
@petition.properties['name'].must_equal "hey world"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should modify a resource with a post request" do
|
31
|
+
@petition.id = 2
|
32
|
+
url_to_be_called = 'https://api.change.org/v1/petitions/2/signatures'
|
33
|
+
params = {
|
34
|
+
:api_key => @api_key_example,
|
35
|
+
:first_name => 'Jean-Luc',
|
36
|
+
:last_name => 'Picard',
|
37
|
+
:city => 'Marseille',
|
38
|
+
:postal_code => '13055',
|
39
|
+
:country_code => 'FR',
|
40
|
+
:email => 'jlp@enterprise1701d.com'
|
41
|
+
}
|
42
|
+
@client.expects(:send)
|
43
|
+
.with('post', url_to_be_called, params)
|
44
|
+
.once
|
45
|
+
.returns(MockResponse.new(200, { "result" => "success" }))
|
46
|
+
@petition.signatures.add_signature(params, { 'auth_key' => 'my_on_call_auth_key', 'source' => 'my_source' }).must_equal true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should raise an exception if the response indicates the request was not a successful" do
|
50
|
+
url_to_be_called = 'https://api.change.org/v1/petitions/1'
|
51
|
+
params = { :api_key => @api_key_example }
|
52
|
+
@client.expects(:send)
|
53
|
+
.with('get', url_to_be_called, params)
|
54
|
+
.once
|
55
|
+
.returns(MockResponse.new(400, { "result" => "failure", "messages" => [ "bad thing one", "bad thing two"] }))
|
56
|
+
lambda { @petition.load(1) }.must_raise(Change::Exceptions::ChangeException)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#final_url' do
|
62
|
+
|
63
|
+
it "should return the proper URL with the host and protocol" do
|
64
|
+
@client.send(:final_url, '/v1/something').must_equal 'https://api.change.org/v1/something'
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#generate_rsig' do
|
70
|
+
|
71
|
+
it "should properly generate the request signature for the given body" do
|
72
|
+
@petition.auth_key = { 'auth_key' => 'my_on_call_auth_key', 'source' => 'my_source' }
|
73
|
+
params = { "first_arg" => "whatever is in the body" }
|
74
|
+
to_digest = HTTParty::HashConversions.to_params(params) + @secret_token_example + @petition.auth_key['auth_key']
|
75
|
+
expected_rsig = Digest::SHA2.hexdigest(to_digest)
|
76
|
+
@client.send(:generate_rsig, params, @petition.auth_key['auth_key']).must_equal expected_rsig
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'util/test_helper'
|
2
|
+
|
3
|
+
class TesterCollection < Change::Resources::CollectionResource; end
|
4
|
+
class TesterMember < Change::Resources::MemberResource
|
5
|
+
attr_accessor :testers
|
6
|
+
|
7
|
+
def initialize(client, properties = {})
|
8
|
+
super(client, properties)
|
9
|
+
@testers = TesterCollection.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'CollectionResource' do
|
15
|
+
|
16
|
+
describe '#collection_name' do
|
17
|
+
|
18
|
+
it "should derive the collection name from the class name before 'Collection'" do
|
19
|
+
correct_collection_name = "testers"
|
20
|
+
TesterCollection.collection_name.must_equal correct_collection_name
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#initialize' do
|
26
|
+
|
27
|
+
it "should initialize the resource with the specified parent resource and set the client to that of the parent" do
|
28
|
+
auth_key = { :auth_key => 'a test' }
|
29
|
+
client = mock
|
30
|
+
parent_resource = TesterMember.new(client)
|
31
|
+
resource = TesterCollection.new(parent_resource)
|
32
|
+
resource.parent_resource.must_equal parent_resource
|
33
|
+
resource.client.must_equal client
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'after initialization' do
|
39
|
+
|
40
|
+
before do
|
41
|
+
@client = mock
|
42
|
+
@parent_resource = TesterMember.new(@client)
|
43
|
+
@resource = TesterCollection.new(@parent_resource)
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#load' do
|
47
|
+
|
48
|
+
it "should load the collection using the parent resource" do
|
49
|
+
params = { :param1 => 1}
|
50
|
+
returned_collection = [1, 2]
|
51
|
+
collection_name = TesterCollection.collection_name.to_sym
|
52
|
+
@parent_resource.expects(:load_collection).with(collection_name, params).once
|
53
|
+
@resource.load(params)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'util/test_helper'
|
2
|
+
|
3
|
+
class WidgetsCollection < Change::Resources::CollectionResource; end
|
4
|
+
class TesterResource < Change::Resources::MemberResource
|
5
|
+
|
6
|
+
attr_accessor :widgets
|
7
|
+
|
8
|
+
def initialize(client, properties = {})
|
9
|
+
super(client, properties)
|
10
|
+
@widgets = WidgetsCollection.new(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'MemberResource' do
|
15
|
+
|
16
|
+
describe '#initialize' do
|
17
|
+
|
18
|
+
it "should initialize the resource with id, properties, and an auth key if specified" do
|
19
|
+
auth_key = { :auth_key => 'a test' }
|
20
|
+
properties = { :id => 3, :auth_key => auth_key, :other => 'what' }
|
21
|
+
resource_with_auth_key = TesterResource.new(@client, properties)
|
22
|
+
resource_with_auth_key.auth_key.must_equal auth_key
|
23
|
+
resource_with_auth_key.id.must_equal 3
|
24
|
+
resource_with_auth_key.properties[:other].must_equal 'what'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'after initialization' do
|
30
|
+
|
31
|
+
before do
|
32
|
+
@client = mock
|
33
|
+
@resource = TesterResource.new(@client)
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#needs_authorization?' do
|
37
|
+
|
38
|
+
it "should return true for :get" do
|
39
|
+
@resource.needs_authorization?(:get).must_equal false
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return false for :post, :put, and :delete" do
|
43
|
+
@resource.needs_authorization?(:post).must_equal true
|
44
|
+
@resource.needs_authorization?(:put).must_equal true
|
45
|
+
@resource.needs_authorization?(:delete).must_equal true
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#auth_key' do
|
51
|
+
|
52
|
+
before do
|
53
|
+
@test_key_1 = { :auth_key => 'test_key', :source => 'test source' }
|
54
|
+
@test_key_2 = { :auth_key => 'test_key2', :source => 'test source 2' }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should add a new auth key to the resource and return the first one or a specific one" do
|
58
|
+
@resource.auth_key = @test_key_1
|
59
|
+
@resource.auth_key.must_equal @test_key_1
|
60
|
+
|
61
|
+
@resource.auth_key = @test_key_2
|
62
|
+
@resource.auth_key.must_equal @test_key_1
|
63
|
+
@resource.auth_key(1).must_equal @test_key_2
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#make_request' do
|
69
|
+
|
70
|
+
before do
|
71
|
+
@resource.id = 1
|
72
|
+
@request_on = :member
|
73
|
+
@request_type = { :method => :get }
|
74
|
+
@params = {}
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should make a call to its client's request method" do
|
78
|
+
@client.expects(:request).with(@request_on, @request_type, @resource, @params).once
|
79
|
+
@resource.make_request(@request_on, @request_type, @params)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#endpoint' do
|
85
|
+
|
86
|
+
before do
|
87
|
+
@resource.id = 2
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return a proper endpoint path for a specific member" do
|
91
|
+
@resource.endpoint(:member).must_equal '/v1/testerresources/2'
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should return a proper endpoint path for a sub-collection of a specific member" do
|
95
|
+
@resource.endpoint(:member, :petitions).must_equal '/v1/testerresources/2/petitions'
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should return a proper endpoint path for resource collection" do
|
99
|
+
@resource.endpoint(:collection).must_equal '/v1/testerresources'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#request_auth_key' do
|
104
|
+
|
105
|
+
it "should return true if an auth key is granted" do
|
106
|
+
@resource.expects(:make_request)
|
107
|
+
.with(:member, { :method => :post, :collection => :auth_keys }, {})
|
108
|
+
.returns({ 'status' => 'granted', 'api_key' => "test" })
|
109
|
+
@resource.request_auth_key({}).must_equal true
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should return false if an auth key is not granted" do
|
113
|
+
@resource.expects(:make_request)
|
114
|
+
.with(:member, { :method => :post, :collection => :auth_keys }, {})
|
115
|
+
.returns({ 'status' => 'denied', 'api_key' => "test" })
|
116
|
+
@resource.request_auth_key({}).must_equal false
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#get_id' do
|
122
|
+
|
123
|
+
it "should make a request to get the ID of the resource by URL" do
|
124
|
+
resource_url = "http://www.change.org/petitions/i-am-a-url"
|
125
|
+
@resource.expects(:make_request)
|
126
|
+
.with(:collection, { :method => :get, :action => :get_id }, { "#{TesterResource.member_name}_url".to_sym => resource_url })
|
127
|
+
.returns({ "#{TesterResource.member_name}_id" => 1 })
|
128
|
+
@resource.get_id(resource_url).must_equal 1
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#load' do
|
136
|
+
|
137
|
+
before do
|
138
|
+
@client = mock
|
139
|
+
@resource = TesterResource.new(@client)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should load the resource by ID" do
|
143
|
+
res_id = 3
|
144
|
+
params = {}
|
145
|
+
@resource.expects(:get_id).never
|
146
|
+
@resource.expects(:make_request)
|
147
|
+
.with(:member, { :method => :get }, params)
|
148
|
+
.returns({
|
149
|
+
'name' => "John",
|
150
|
+
'age' => 43
|
151
|
+
})
|
152
|
+
@resource.load(res_id)
|
153
|
+
@resource.id.must_equal 3
|
154
|
+
@resource.properties['name'].must_equal "John"
|
155
|
+
@resource.properties['age'].must_equal 43
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should load the resource by URL" do
|
159
|
+
resource_url = 'http://www.change.org/petitions/example'
|
160
|
+
params = {}
|
161
|
+
@resource.expects(:get_id).with(resource_url).returns(93)
|
162
|
+
@resource.expects(:make_request)
|
163
|
+
.with(:member, { :method => :get }, params)
|
164
|
+
.returns({
|
165
|
+
'name' => "Billy Jo",
|
166
|
+
'age' => 8
|
167
|
+
})
|
168
|
+
@resource.load(resource_url)
|
169
|
+
@resource.id.must_equal 93
|
170
|
+
@resource.properties['name'].must_equal "Billy Jo"
|
171
|
+
@resource.properties['age'].must_equal 8
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
describe '#load_collection' do
|
177
|
+
|
178
|
+
before do
|
179
|
+
@client = mock
|
180
|
+
@resource = TesterResource.new(@client)
|
181
|
+
@params = {}
|
182
|
+
@returned_widgets = [1, 3, 5, 7]
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should load collection by dumping the returned array if that's what is returned" do
|
186
|
+
@resource.widgets.collection.must_be_nil
|
187
|
+
@resource.expects(:make_request)
|
188
|
+
.with(:member, { :method => :get, :collection => :widgets }, @params)
|
189
|
+
.returns(@returned_widgets)
|
190
|
+
@resource.load_collection(:widgets)
|
191
|
+
@resource.widgets.collection.must_equal @returned_widgets
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should load collection by getting the matching object attribute and dumping that array" do
|
195
|
+
@resource.widgets.collection.must_be_nil
|
196
|
+
@resource.expects(:make_request)
|
197
|
+
.with(:member, { :method => :get, :collection => :widgets }, @params)
|
198
|
+
.returns({
|
199
|
+
'an_attr' => 'something',
|
200
|
+
'name' => 'hello',
|
201
|
+
'widgets' => @returned_widgets
|
202
|
+
})
|
203
|
+
@resource.load_collection(:widgets)
|
204
|
+
@resource.widgets.collection.must_equal @returned_widgets
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'util/test_helper'
|
2
|
+
|
3
|
+
describe 'SignatureCollection' do
|
4
|
+
|
5
|
+
describe '#auth_key' do
|
6
|
+
|
7
|
+
before do
|
8
|
+
client = mock
|
9
|
+
@test_key = 'my_test_key'
|
10
|
+
@parent_resource = Change::Resources::Petition.new(client, { :auth_key => { 'auth_key' => @test_key } })
|
11
|
+
@signature_collection = Change::Resources::SignatureCollection.new(@parent_resource)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return the parent resource's auth key" do
|
15
|
+
@signature_collection.auth_key['auth_key'].must_equal @test_key
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#add_signature' do
|
21
|
+
|
22
|
+
before do
|
23
|
+
@client = mock
|
24
|
+
@params = {
|
25
|
+
:first_name => 'Jean-Luc',
|
26
|
+
:last_name => 'Picard',
|
27
|
+
:city => 'Marseille',
|
28
|
+
:postal_code => '13055',
|
29
|
+
:country_code => 'FR',
|
30
|
+
:email => 'jlp@enterprise1701d.com'
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "when there is already an auth key on the parent resource" do
|
35
|
+
|
36
|
+
before do
|
37
|
+
@test_key = 'my_test_key'
|
38
|
+
@parent_resource = Change::Resources::Petition.new(@client, { :auth_key => { 'auth_key' => @test_key } })
|
39
|
+
@signature_collection = Change::Resources::SignatureCollection.new(@parent_resource)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should post a signature to its parent petition and return true if successful" do
|
43
|
+
@signature_collection.expects(:make_request)
|
44
|
+
.with(:collection, { :method => :post }, @params)
|
45
|
+
.returns({ 'result' => 'success' })
|
46
|
+
@signature_collection.add_signature(@params).must_equal true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should post a signature to its parent petition and return false if unsuccessful" do
|
50
|
+
@signature_collection.expects(:make_request)
|
51
|
+
.with(:collection, { :method => :post }, @params)
|
52
|
+
.returns({ 'result' => 'failure' })
|
53
|
+
@signature_collection.add_signature(@params).must_equal false
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when there is no auth key on the parent resource" do
|
59
|
+
|
60
|
+
before do
|
61
|
+
@parent_resource = Change::Resources::Petition.new(@client)
|
62
|
+
@signature_collection = Change::Resources::SignatureCollection.new(@parent_resource)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise an error when trying to post a signature" do
|
66
|
+
@signature_collection.expects(:make_request).never
|
67
|
+
lambda { @signature_collection.add_signature(@params) }.must_raise(RuntimeError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should succeed in adding a signature if an auth key is specified during the call" do
|
71
|
+
on_call_auth_key = {
|
72
|
+
'auth_key' => 'my_on_call_auth_key',
|
73
|
+
'source' => 'my_source'
|
74
|
+
}
|
75
|
+
@signature_collection.expects(:make_request)
|
76
|
+
.with(:collection, { :method => :post }, @params)
|
77
|
+
.returns({ 'result' => 'success' })
|
78
|
+
@signature_collection.add_signature(@params, on_call_auth_key).must_equal true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: change-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Eric Lukoff
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.10.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.10.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mocha
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: A Ruby library for the Change.org API.
|
63
|
+
email: eric@ericlukoff.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- lib/change-ruby.rb
|
69
|
+
- lib/exceptions/change_exception.rb
|
70
|
+
- lib/requests/client.rb
|
71
|
+
- lib/requests/const.rb
|
72
|
+
- lib/resources/collection_resource.rb
|
73
|
+
- lib/resources/member_resource.rb
|
74
|
+
- lib/resources/organization.rb
|
75
|
+
- lib/resources/petition.rb
|
76
|
+
- lib/resources/petition_collection.rb
|
77
|
+
- lib/resources/reason_collection.rb
|
78
|
+
- lib/resources/resource.rb
|
79
|
+
- lib/resources/signature_collection.rb
|
80
|
+
- lib/resources/target_collection.rb
|
81
|
+
- lib/resources/update_collection.rb
|
82
|
+
- lib/resources/user.rb
|
83
|
+
- test/client_test.rb
|
84
|
+
- test/collection_resource_test.rb
|
85
|
+
- test/member_resource_test.rb
|
86
|
+
- test/signature_collection_resource_test.rb
|
87
|
+
- test/util/test_helper.rb
|
88
|
+
homepage: http://rubygems.org/gems/change-ruby
|
89
|
+
licenses: []
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.8.23
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Change.org API Ruby Library (unofficial)
|
112
|
+
test_files: []
|
113
|
+
has_rdoc:
|