change-ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|