uservoice-ruby 0.0.5 → 0.0.6

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.
@@ -0,0 +1,132 @@
1
+ module UserVoice
2
+ class Client
3
+
4
+ def initialize(*args)
5
+ case args.size
6
+ when 3,4
7
+ init_subdomain_and_api_keys(*args)
8
+ when 1,2
9
+ init_consumer_and_access_token(*args)
10
+ end
11
+ end
12
+
13
+ def init_subdomain_and_api_keys(subdomain_name, api_key, api_secret, attrs={})
14
+ consumer = OAuth::Consumer.new(api_key, api_secret, {
15
+ :site => "#{attrs[:protocol] || 'https'}://#{subdomain_name}.#{attrs[:uservoice_domain] || 'uservoice.com'}"
16
+ })
17
+ init_consumer_and_access_token(consumer, attrs)
18
+ end
19
+
20
+ def init_consumer_and_access_token(consumer, attrs={})
21
+ @consumer = consumer
22
+ @token = OAuth::AccessToken.new(@consumer, attrs[:oauth_token] || '', attrs[:oauth_token_secret] || '')
23
+ @response_format = attrs[:response_format] || :hash
24
+ @callback = attrs[:callback]
25
+ end
26
+
27
+ def authorize_url
28
+ request_token.authorize_url
29
+ end
30
+
31
+ def login_with_verifier(oauth_verifier)
32
+ raise Unauthorized.new('Call request token first') if @request_token.nil?
33
+ token = @request_token.get_access_token(:oauth_verifier => oauth_verifier)
34
+ Client.new(@consumer, :oauth_token => token.token, :oauth_token_secret => token.secret)
35
+ end
36
+
37
+ def login_with_access_token(oauth_token, oauth_token_secret, &block)
38
+ token = Client.new(@consumer, :oauth_token => oauth_token, :oauth_token_secret => oauth_token_secret)
39
+ if block_given?
40
+ yield token
41
+ else
42
+ return token
43
+ end
44
+ end
45
+
46
+ def token
47
+ @token.token
48
+ end
49
+
50
+ def secret
51
+ @token.secret
52
+ end
53
+
54
+ def request_token
55
+ @request_token = @consumer.get_request_token(:oauth_callback => @callback)
56
+ end
57
+
58
+ def login_as_owner(&block)
59
+ token = post('/api/v1/users/login_as_owner.json', {
60
+ 'request_token' => request_token.token
61
+ })['token']
62
+ if token
63
+ login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
64
+ else
65
+ raise Unauthorized.new("Could not get Access Token")
66
+ end
67
+ end
68
+
69
+ def login_as(email, &block)
70
+ unless email.to_s.match(EMAIL_FORMAT)
71
+ raise Unauthorized.new("'#{email}' is not a valid email address")
72
+ end
73
+ token = post('/api/v1/users/login_as.json', {
74
+ :user => { :email => email },
75
+ :request_token => request_token.token
76
+ })['token']
77
+
78
+ if token
79
+ login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
80
+ else
81
+ raise Unauthorized.new("Could not get Access Token")
82
+ end
83
+ end
84
+
85
+ def request(method, uri, request_body={}, headers={})
86
+ headers = DEFAULT_HEADERS.merge(headers)
87
+
88
+ if headers['Content-Type'] == 'application/json' && request_body.is_a?(Hash)
89
+ request_body = request_body.to_json
90
+ end
91
+
92
+ response = case method.to_sym
93
+ when :post, :put
94
+ @token.request(method, uri, request_body, headers)
95
+ when :head, :delete, :get
96
+ @token.request(method, uri, headers)
97
+ else
98
+ raise RuntimeError.new("Invalid HTTP method #{method}")
99
+ end
100
+
101
+ return case @response_format.to_s
102
+ when 'raw'
103
+ response
104
+ else
105
+ attrs = JSON.parse(response.body)
106
+ if attrs && attrs['errors']
107
+ case attrs['errors']['type']
108
+ when 'unauthorized'
109
+ raise Unauthorized.new(attrs)
110
+ when 'record_not_found'
111
+ raise NotFound.new(attrs)
112
+ when 'application_error'
113
+ raise ApplicationError.new(attrs)
114
+ else
115
+ raise APIError.new(attrs)
116
+ end
117
+ end
118
+ attrs
119
+ end
120
+ end
121
+
122
+ %w(get post delete put).each do |method|
123
+ define_method(method) do |*args|
124
+ request(method, *args)
125
+ end
126
+ end
127
+
128
+ def get_collection(uri, opts={})
129
+ UserVoice::Collection.new(self, uri, opts)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,63 @@
1
+ module UserVoice
2
+ class Collection
3
+ def initialize(client, query, opts={})
4
+ @client = client
5
+ @query = query
6
+ @limit = opts[:limit] || 2**60
7
+ @per_page = [@limit, 500].min
8
+ @pages = {}
9
+ end
10
+
11
+ def first
12
+ load_record(0)
13
+ end
14
+
15
+ def last
16
+ load_record(size() - 1)
17
+ end
18
+
19
+ def size
20
+ if @response_data.nil?
21
+ load_record(0)
22
+ end
23
+ @response_data['total_records']
24
+ end
25
+
26
+ def map
27
+ index = 0
28
+ records = []
29
+ while record = load_record(index)
30
+ records.push(yield record)
31
+ index += 1
32
+ end
33
+ return records
34
+ end
35
+ alias collect map
36
+
37
+ def each
38
+ map do |value|
39
+ yield value
40
+ value
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def load_record(i)
47
+ load_page((i/500.0).floor + 1)[i%500]
48
+ end
49
+
50
+ def load_page(i)
51
+ if @pages[i].nil?
52
+ result = @client.get("#{@query}#{@query.include?('?') ? '&' : '?'}per_page=#{@per_page}&page=#{i}")
53
+
54
+ if @response_data = result.delete('response_data')
55
+ @pages[i] = result.shift.last if result.first
56
+ else
57
+ raise UserVoice::NotFound.new('The resource you requested is not a collection')
58
+ end
59
+ end
60
+ return @pages[i]
61
+ end
62
+ end
63
+ end
@@ -1,4 +1,6 @@
1
1
  require "uservoice/version"
2
+ require 'uservoice/collection'
3
+ require 'uservoice/client'
2
4
  require 'rubygems'
3
5
  require 'ezcrypto'
4
6
  require 'json'
@@ -31,131 +33,4 @@ module UserVoice
31
33
 
32
34
  return CGI.escape(encoded)
33
35
  end
34
-
35
- class Client
36
-
37
- def initialize(*args)
38
- case args.size
39
- when 3,4
40
- init_subdomain_and_api_keys(*args)
41
- when 1,2
42
- init_consumer_and_access_token(*args)
43
- end
44
- end
45
-
46
- def init_subdomain_and_api_keys(subdomain_name, api_key, api_secret, attrs={})
47
- consumer = OAuth::Consumer.new(api_key, api_secret, {
48
- :site => "#{attrs[:protocol] || 'https'}://#{subdomain_name}.#{attrs[:uservoice_domain] || 'uservoice.com'}"
49
- })
50
- init_consumer_and_access_token(consumer, attrs)
51
- end
52
-
53
- def init_consumer_and_access_token(consumer, attrs={})
54
- @consumer = consumer
55
- @token = OAuth::AccessToken.new(@consumer, attrs[:oauth_token] || '', attrs[:oauth_token_secret] || '')
56
- @response_format = attrs[:response_format] || :hash
57
- @callback = attrs[:callback]
58
- end
59
-
60
- def authorize_url
61
- request_token.authorize_url
62
- end
63
-
64
- def login_with_verifier(oauth_verifier)
65
- raise Unauthorized.new('Call request token first') if @request_token.nil?
66
- token = @request_token.get_access_token(:oauth_verifier => oauth_verifier)
67
- Client.new(@consumer, :oauth_token => token.token, :oauth_token_secret => token.secret)
68
- end
69
-
70
- def login_with_access_token(oauth_token, oauth_token_secret, &block)
71
- token = Client.new(@consumer, :oauth_token => oauth_token, :oauth_token_secret => oauth_token_secret)
72
- if block_given?
73
- yield token
74
- else
75
- return token
76
- end
77
- end
78
-
79
- def token
80
- @token.token
81
- end
82
-
83
- def secret
84
- @token.secret
85
- end
86
-
87
- def request_token
88
- @request_token = @consumer.get_request_token(:oauth_callback => @callback)
89
- end
90
-
91
- def login_as_owner(&block)
92
- token = post('/api/v1/users/login_as_owner.json', {
93
- 'request_token' => request_token.token
94
- })['token']
95
- if token
96
- login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
97
- else
98
- raise Unauthorized.new("Could not get Access Token")
99
- end
100
- end
101
-
102
- def login_as(email, &block)
103
- unless email.to_s.match(EMAIL_FORMAT)
104
- raise Unauthorized.new("'#{email}' is not a valid email address")
105
- end
106
- token = post('/api/v1/users/login_as.json', {
107
- :user => { :email => email },
108
- :request_token => request_token.token
109
- })['token']
110
-
111
- if token
112
- login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
113
- else
114
- raise Unauthorized.new("Could not get Access Token")
115
- end
116
- end
117
-
118
- def request(method, uri, request_body={}, headers={})
119
- headers = DEFAULT_HEADERS.merge(headers)
120
-
121
- if headers['Content-Type'] == 'application/json' && request_body.is_a?(Hash)
122
- request_body = request_body.to_json
123
- end
124
-
125
- response = case method.to_sym
126
- when :post, :put
127
- @token.request(method, uri, request_body, headers)
128
- when :head, :delete, :get
129
- @token.request(method, uri, headers)
130
- else
131
- raise RuntimeError.new("Invalid HTTP method #{method}")
132
- end
133
-
134
- return case @response_format.to_s
135
- when 'raw'
136
- response
137
- else
138
- attrs = JSON.parse(response.body)
139
- if attrs && attrs['errors']
140
- case attrs['errors']['type']
141
- when 'unauthorized'
142
- raise Unauthorized.new(attrs)
143
- when 'record_not_found'
144
- raise NotFound.new(attrs)
145
- when 'application_error'
146
- raise ApplicationError.new(attrs)
147
- else
148
- raise APIError.new(attrs)
149
- end
150
- end
151
- attrs
152
- end
153
- end
154
-
155
- %w(get post delete put).each do |method|
156
- define_method(method) do |*args|
157
- request(method, *args)
158
- end
159
- end
160
- end
161
36
  end
@@ -1,3 +1,3 @@
1
1
  module Uservoice
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -12,175 +12,4 @@ describe UserVoice do
12
12
  key = EzCrypto::Key.with_password(config['subdomain_name'], config['sso_key'])
13
13
  key.decrypt(encrypted_raw_data).should match('mailaddress@example.com')
14
14
  end
15
-
16
- describe UserVoice::Client do
17
- subject { UserVoice::Client.new(config['subdomain_name'],
18
- config['api_key'],
19
- config['api_secret'],
20
- :uservoice_domain => config['uservoice_domain'],
21
- :protocol => config['protocol']) }
22
-
23
- it "should get user names from the API" do
24
- users = subject.get("/api/v1/users.json?per_page=3")
25
- user_names = users['users'].map { |user| user['name'] }
26
- user_names.all?.should == true
27
- user_names.size.should == 3
28
- end
29
-
30
- it "should not get current user without logged in user" do
31
- lambda do
32
- user = subject.get("/api/v1/users/current.json")
33
- end.should raise_error(UserVoice::Unauthorized)
34
- end
35
-
36
- it "should be able to get access token as owner" do
37
- subject.login_as_owner do |owner|
38
- owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
39
-
40
- owner.login_as('regular@example.com') do |regular|
41
- owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
42
- @user = regular.get("/api/v1/users/current.json")['user']
43
- @user['roles']['owner'].should == false
44
- end
45
-
46
- owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
47
- end
48
- # ensure blocks got run
49
- @user['email'].should == 'regular@example.com'
50
- end
51
-
52
- it "should not be able to create KB article as nobody" do
53
- lambda do
54
- result = subject.post("/api/v1/articles.json", :article => {
55
- :title => 'good morning'
56
- })
57
- end.should raise_error(UserVoice::Unauthorized)
58
- end
59
-
60
- it "should be able to create and delete a forum as the owner" do
61
- owner = subject.login_as_owner
62
- forum = owner.post("/api/v1/forums.json", :forum => {
63
- :name => 'Test forum from RSpec',
64
- 'private' => true,
65
- 'allow_by_email_domain' => true,
66
- 'allowed_email_domains' => [{'domain' => 'raimo.rspec.example.com'}]
67
- })['forum']
68
-
69
- forum['id'].should be_a(Integer)
70
-
71
- deleted_forum = owner.delete("/api/v1/forums/#{forum['id']}.json")['forum']
72
- deleted_forum['id'].should == forum['id']
73
- end
74
-
75
- it "should get current user with 2-legged call" do
76
- user = subject.login_as('mailaddress@example.com') do |token|
77
- token.get("/api/v1/users/current.json")['user']
78
- end
79
-
80
- user['email'].should == 'mailaddress@example.com'
81
- end
82
-
83
- it "should get current user with copied access token" do
84
- original_token = subject.login_as('mailaddress@example.com')
85
-
86
- client = UserVoice::Client.new(config['subdomain_name'],
87
- config['api_key'],
88
- config['api_secret'],
89
- :uservoice_domain => config['uservoice_domain'],
90
- :protocol => config['protocol'],
91
- :oauth_token => original_token.token,
92
- :oauth_token_secret => original_token.secret)
93
- # Also this works but creates an extra object:
94
- # client = client.login_with_access_token(original_token.token, original_token.secret)
95
-
96
- user = client.get("/api/v1/users/current.json")['user']
97
-
98
- user['email'].should == 'mailaddress@example.com'
99
- end
100
-
101
- it "should login as an owner" do
102
- me = subject.login_as_owner
103
-
104
- owner = me.get("/api/v1/users/current.json")['user']
105
- owner['roles']['owner'].should == true
106
- end
107
-
108
- it "should not be able to delete when not deleting on behalf of anyone" do
109
- lambda {
110
- result = subject.delete("/api/v1/users/#{234}.json")
111
- }.should raise_error(UserVoice::Unauthorized, /user required/i)
112
- end
113
-
114
- it "should not be able to delete owner" do
115
- owner_access_token = subject.login_as_owner
116
-
117
- owner = owner_access_token.get("/api/v1/users/current.json")['user']
118
-
119
- lambda {
120
- result = owner_access_token.delete("/api/v1/users/#{owner['id']}.json")
121
- }.should raise_error(UserVoice::Unauthorized, /last owner/i)
122
- end
123
-
124
- it "should not be able to delete user without login" do
125
- regular_user = subject.login_as('somebodythere@example.com').get("/api/v1/users/current.json")['user']
126
-
127
- lambda {
128
- subject.delete("/api/v1/users/#{regular_user['id']}.json")
129
- }.should raise_error(UserVoice::Unauthorized)
130
- end
131
-
132
- it "should be able to identify suggestions" do
133
- owner_token = subject.login_as_owner
134
- external_scope='sync_to_moon'
135
- suggestions = owner_token.get("/api/v1/suggestions.json?filter=with_external_id&external_scope=#{external_scope}&manual_action=#{external_scope}")['suggestions']
136
-
137
- identifications = suggestions.map {|s| { :id => s['id'], :external_id => s['id'].to_i*10 } }
138
-
139
- ids = owner_token.put("/api/v1/suggestions/identify.json",
140
- :external_scope => external_scope,
141
- :identifications => identifications)['identifications']['ids']
142
- ids.should == identifications.map { |s| s[:id] }.sort
143
- end
144
-
145
- it "should be able to delete itself" do
146
- my_token = subject.login_as('somebodythere@example.com')
147
-
148
- # whoami
149
- my_id = my_token.get("/api/v1/users/current.json")['user']['id']
150
-
151
- # Delete myself!
152
- my_token.delete("/api/v1/users/#{my_id}.json")['user']['id'].should == my_id
153
-
154
- # I don't exist anymore
155
- lambda {
156
- my_token.get("/api/v1/users/current.json")
157
- }.should raise_error(UserVoice::NotFound)
158
- end
159
-
160
- it "should/be able to delete random user and login as him after that" do
161
- somebody = subject.login_as('somebodythere@example.com')
162
- owner = subject.login_as_owner
163
-
164
- # somebody is still there...
165
- regular_user = somebody.get("/api/v1/users/current.json")['user']
166
- regular_user['email'].should == 'somebodythere@example.com'
167
-
168
- # delete somebody!
169
- owner.delete("/api/v1/users/#{regular_user['id']}.json")['user']['id'].should == regular_user['id']
170
-
171
- # not found anymore!
172
- lambda {
173
- somebody.get("/api/v1/users/current.json")['errors']['type']
174
- }.should raise_error(UserVoice::NotFound)
175
-
176
- # this recreates somebody
177
- somebody = subject.login_as('somebodythere@example.com')
178
- somebody.get("/api/v1/users/current.json")['user']['id'].should_not == regular_user['id']
179
- end
180
-
181
- it "should raise error with invalid email parameter" do
182
- expect { subject.login_as('ma') }.to raise_error(UserVoice::Unauthorized)
183
- expect { subject.login_as(nil) }.to raise_error(UserVoice::Unauthorized)
184
- end
185
- end
186
15
  end
@@ -0,0 +1,202 @@
1
+ require 'spec_helper'
2
+ describe UserVoice::Client do
3
+ subject { UserVoice::Client.new(config['subdomain_name'],
4
+ config['api_key'],
5
+ config['api_secret'],
6
+ :uservoice_domain => config['uservoice_domain'],
7
+ :protocol => config['protocol']) }
8
+ let(:external_scope) { 'external_system_name' }
9
+
10
+ it "should get user names from the API" do
11
+ users = subject.get("/api/v1/users.json?per_page=3")
12
+ user_names = users['users'].map { |user| user['name'] }
13
+ user_names.all?.should == true
14
+ user_names.size.should == 3
15
+ end
16
+
17
+ it "should not get current user without logged in user" do
18
+ lambda do
19
+ user = subject.get("/api/v1/users/current.json")
20
+ end.should raise_error(UserVoice::Unauthorized)
21
+ end
22
+
23
+ it "should be able to get access token as owner" do
24
+ subject.login_as_owner do |owner|
25
+ owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
26
+
27
+ owner.login_as('regular@example.com') do |regular|
28
+ owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
29
+ @user = regular.get("/api/v1/users/current.json")['user']
30
+ @user['roles']['owner'].should == false
31
+ end
32
+
33
+ owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
34
+ end
35
+ # ensure blocks got run
36
+ @user['email'].should == 'regular@example.com'
37
+ end
38
+
39
+ it "should not be able to create KB article as nobody" do
40
+ lambda do
41
+ result = subject.post("/api/v1/articles.json", :article => {
42
+ :title => 'good morning'
43
+ })
44
+ end.should raise_error(UserVoice::Unauthorized)
45
+ end
46
+
47
+ it "should be able to create and delete a forum as the owner" do
48
+ owner = subject.login_as_owner
49
+ forum = owner.post("/api/v1/forums.json", :forum => {
50
+ :name => 'Test forum from RSpec',
51
+ 'private' => true,
52
+ 'allow_by_email_domain' => true,
53
+ 'allowed_email_domains' => [{'domain' => 'raimo.rspec.example.com'}]
54
+ })['forum']
55
+
56
+ forum['id'].should be_a(Integer)
57
+
58
+ deleted_forum = owner.delete("/api/v1/forums/#{forum['id']}.json")['forum']
59
+ deleted_forum['id'].should == forum['id']
60
+ end
61
+
62
+ it "should get current user with 2-legged call" do
63
+ user = subject.login_as('mailaddress@example.com') do |token|
64
+ token.get("/api/v1/users/current.json")['user']
65
+ end
66
+
67
+ user['email'].should == 'mailaddress@example.com'
68
+ end
69
+
70
+ it "should get current user with copied access token" do
71
+ original_token = subject.login_as('mailaddress@example.com')
72
+
73
+ client = UserVoice::Client.new(config['subdomain_name'],
74
+ config['api_key'],
75
+ config['api_secret'],
76
+ :uservoice_domain => config['uservoice_domain'],
77
+ :protocol => config['protocol'],
78
+ :oauth_token => original_token.token,
79
+ :oauth_token_secret => original_token.secret)
80
+ # Also this works but creates an extra object:
81
+ # client = client.login_with_access_token(original_token.token, original_token.secret)
82
+
83
+ user = client.get("/api/v1/users/current.json")['user']
84
+
85
+ user['email'].should == 'mailaddress@example.com'
86
+ end
87
+
88
+ it "should login as an owner" do
89
+ me = subject.login_as_owner
90
+
91
+ owner = me.get("/api/v1/users/current.json")['user']
92
+ owner['roles']['owner'].should == true
93
+ end
94
+
95
+ it "should not be able to delete when not deleting on behalf of anyone" do
96
+ lambda {
97
+ result = subject.delete("/api/v1/users/#{234}.json")
98
+ }.should raise_error(UserVoice::Unauthorized, /user required/i)
99
+ end
100
+
101
+ it "should not be able to delete owner" do
102
+ owner_access_token = subject.login_as_owner
103
+
104
+ owner = owner_access_token.get("/api/v1/users/current.json")['user']
105
+
106
+ lambda {
107
+ result = owner_access_token.delete("/api/v1/users/#{owner['id']}.json")
108
+ }.should raise_error(UserVoice::Unauthorized, /last owner/i)
109
+ end
110
+
111
+ it "should not be able to delete user without login" do
112
+ regular_user = subject.login_as('somebodythere@example.com').get("/api/v1/users/current.json")['user']
113
+
114
+ lambda {
115
+ subject.delete("/api/v1/users/#{regular_user['id']}.json")
116
+ }.should raise_error(UserVoice::Unauthorized)
117
+ end
118
+
119
+ it 'should get all suggestions using a collection enumerator' do
120
+ subject.should_receive(:get).once.and_return({
121
+ "response_data"=>{"page"=>1, "per_page"=>10, "total_records"=>1, "filter"=>"all", "sort"=>"votes"},
122
+ "suggestions"=>[ {
123
+ "url"=>"http://uservoice-subdomain.uservoice.com/forums/1-a/suggestions/1-i",
124
+ "id"=>1,
125
+ "state"=>"published",
126
+ "title"=>"a",
127
+ "text"=>"b",
128
+ "formatted_text"=>"b",
129
+ "forum"=>{"id"=>"1", "name"=>"General"}
130
+ }
131
+ ]})
132
+ suggestions = subject.get_collection("/api/v1/suggestions.json")
133
+ count = 0
134
+ suggestions.each do |suggestion|
135
+ count += 1
136
+ end
137
+ count.should == suggestions.size
138
+ count.should == 1
139
+ end
140
+
141
+ it "should get an error when trying to query suggestions with an unexistant manual action" do
142
+ lambda {
143
+ subject.login_as_owner do |owner_token|
144
+ owner_token.get("/api/v1/suggestions.json?filter=with_external_id&external_scope=#{external_scope}&manual_action=#{external_scope}")['suggestions']
145
+ end
146
+ }.should raise_error(UserVoice::NotFound)
147
+ end
148
+
149
+ it "should identify a suggestion" do
150
+ owner_token = subject.login_as_owner
151
+
152
+ suggestions = owner_token.get("/api/v1/suggestions.json?filter=without_external_id&external_scope=#{external_scope}&per_page=1")['suggestions']
153
+ identifications = suggestions.map {|s| { :id => s['id'], :external_id => s['id'].to_i*10, :url => 'http://url.example.com' } }
154
+
155
+ ids = owner_token.put("/api/v1/suggestions/identify.json",
156
+ :upsert => true,
157
+ :external_scope => external_scope,
158
+ :identifications => identifications)['identifications']['ids']
159
+ ids.should == identifications.map { |s| s[:id] }.sort
160
+ end
161
+
162
+ it "should be able to delete itself" do
163
+ my_token = subject.login_as('somebodythere@example.com')
164
+
165
+ # whoami
166
+ my_id = my_token.get("/api/v1/users/current.json")['user']['id']
167
+
168
+ # Delete myself!
169
+ my_token.delete("/api/v1/users/#{my_id}.json")['user']['id'].should == my_id
170
+
171
+ # I don't exist anymore
172
+ lambda {
173
+ my_token.get("/api/v1/users/current.json")
174
+ }.should raise_error(UserVoice::NotFound)
175
+ end
176
+
177
+ it "should/be able to delete random user and login as him after that" do
178
+ somebody = subject.login_as('somebodythere@example.com')
179
+ owner = subject.login_as_owner
180
+
181
+ # somebody is still there...
182
+ regular_user = somebody.get("/api/v1/users/current.json")['user']
183
+ regular_user['email'].should == 'somebodythere@example.com'
184
+
185
+ # delete somebody!
186
+ owner.delete("/api/v1/users/#{regular_user['id']}.json")['user']['id'].should == regular_user['id']
187
+
188
+ # not found anymore!
189
+ lambda {
190
+ somebody.get("/api/v1/users/current.json")['errors']['type']
191
+ }.should raise_error(UserVoice::NotFound)
192
+
193
+ # this recreates somebody
194
+ somebody = subject.login_as('somebodythere@example.com')
195
+ somebody.get("/api/v1/users/current.json")['user']['id'].should_not == regular_user['id']
196
+ end
197
+
198
+ it "should raise error with invalid email parameter" do
199
+ expect { subject.login_as('ma') }.to raise_error(UserVoice::Unauthorized)
200
+ expect { subject.login_as(nil) }.to raise_error(UserVoice::Unauthorized)
201
+ end
202
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe UserVoice::Collection do
4
+ PER_PAGE = 500
5
+ ELEMENTS = 1501 # 4 pages, one record in the last page
6
+
7
+ context 'having an empty result set' do
8
+ let(:client) do
9
+ client = mock(
10
+ :get => {"response_data"=>{"page"=>1, "per_page"=>10, "total_records"=>0, "filter"=>"all", "sort"=>"votes"}, "suggestions"=>[]}
11
+ )
12
+ end
13
+ before do
14
+ @collection = UserVoice::Collection.new(client, '/api/v1/suggestions')
15
+ end
16
+
17
+ it "should return size of zero" do
18
+ @collection.size.should == 0
19
+ end
20
+
21
+ it 'should have zero entries with #each' do
22
+ @collection.each do |suggestion|
23
+ raise RuntimeError.new('should be empty')
24
+ end
25
+ end
26
+ end
27
+
28
+ context 'having a list with one element' do
29
+ before do
30
+ @client = mock(
31
+ :get => {"response_data"=>{"page"=>1, "per_page"=>10, "total_records"=>1, "filter"=>"all", "sort"=>"votes"}, "suggestions"=>[ {
32
+ "url"=>"http://uservoice-subdomain.uservoice.com/forums/1-general/suggestions/1-idea",
33
+ "id"=>1,
34
+ "state"=>"published",
35
+ "title"=>"a",
36
+ "text"=>"b",
37
+ "formatted_text"=>"b",
38
+ "forum"=>{"id"=>"1", "name"=>"General"}
39
+ }
40
+ ]})
41
+ @collection = UserVoice::Collection.new(@client, '/api/v1/suggestions')
42
+ end
43
+
44
+ it 'should have correct size' do
45
+ @collection.size.should == 1
46
+ end
47
+
48
+ it 'should yield correct records ids' do
49
+ ids = []
50
+ @collection.each do |val|
51
+ ids.push(val['id'])
52
+ end
53
+ ids.should == [1]
54
+ end
55
+
56
+ it 'should map ids' do
57
+ @collection.map do |val|
58
+ val['id']
59
+ end.should == [1]
60
+ end
61
+
62
+ it 'should collect ids' do
63
+ @client.should_receive(:get).with("/api/v1/suggestions?per_page=#{PER_PAGE}&page=1").once
64
+
65
+ @collection.collect do |val|
66
+ val['id']
67
+ end.should == [1]
68
+
69
+ @collection.map do |val|
70
+ val['id']
71
+ end.should == [1]
72
+ end
73
+ end
74
+
75
+ context 'having a list with 1501 elements' do
76
+
77
+ before do
78
+ @client = mock()
79
+
80
+ 4.times.map do |page_index|
81
+ @client.stub(:get).with("/api/v1/suggestions?per_page=#{PER_PAGE}&page=#{page_index+1}") do
82
+ {
83
+ "response_data" => {"page"=> page_index+1, "per_page" => PER_PAGE, "total_records" => ELEMENTS, "filter"=>"all", "sort"=>"votes"},
84
+ "suggestions"=> (PER_PAGE * page_index + 1).upto([PER_PAGE * (page_index + 1), ELEMENTS].min).map do |idea_index|
85
+ {
86
+ "url"=>"http://uservoice-subdomain.uservoice.com/forums/1-general/suggestions/#{idea_index}-idea",
87
+ "id"=> idea_index,
88
+ "state"=>"published",
89
+ "title"=>"Idea ##{idea_index}",
90
+ "text"=>"Idea ##{idea_index}",
91
+ "formatted_text"=>"Idea ##{idea_index}",
92
+ "forum"=>{"id"=>"1", "name"=>"General"}
93
+ }
94
+ end
95
+ }
96
+ end
97
+ end
98
+ @collection = UserVoice::Collection.new(@client, '/api/v1/suggestions')
99
+ end
100
+
101
+ it 'should have correct size' do
102
+ @client.should_receive(:get).with("/api/v1/suggestions?per_page=#{PER_PAGE}&page=1").once
103
+ @collection.size.should == ELEMENTS
104
+ end
105
+
106
+ it 'should get last element and array size with two api calls' do
107
+ @collection.last['id'].should == ELEMENTS
108
+ @collection.first['id'].should == 1
109
+ end
110
+
111
+ it 'should yield correct records ids' do
112
+ ids = []
113
+ @collection.each do |val|
114
+ ids.push(val['id'])
115
+ end
116
+ ids.size.should == ELEMENTS
117
+ ids.should == 1.upto(ELEMENTS).to_a
118
+ end
119
+
120
+ it 'should map ids' do
121
+ @collection.map do |val|
122
+ val['id']
123
+ end.should == 1.upto(ELEMENTS).to_a
124
+ end
125
+ end
126
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uservoice-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-25 00:00:00.000000000 Z
12
+ date: 2012-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70266109840080 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 1.0.5
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70266109840080
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.5
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: ezcrypto
27
- requirement: &70266109839480 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 0.7.2
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *70266109839480
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.7.2
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: json
38
- requirement: &70266109838900 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: 1.7.5
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *70266109838900
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.7.5
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: oauth
49
- requirement: &70266109838340 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,7 +69,12 @@ dependencies:
54
69
  version: 0.4.7
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *70266109838340
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.4.7
58
78
  description: The gem provides Ruby-bindings to UserVoice API and helps generating
59
79
  Single-Sign-On tokens.
60
80
  email:
@@ -68,10 +88,14 @@ files:
68
88
  - README.md
69
89
  - Rakefile
70
90
  - lib/uservoice-ruby.rb
91
+ - lib/uservoice/client.rb
92
+ - lib/uservoice/collection.rb
71
93
  - lib/uservoice/user_voice.rb
72
94
  - lib/uservoice/version.rb
73
95
  - spec/config.yml.templ
74
96
  - spec/lib/user_voice_spec.rb
97
+ - spec/lib/uservoice/client_spec.rb
98
+ - spec/lib/uservoice/collection_spec.rb
75
99
  - spec/spec_helper.rb
76
100
  - uservoice-ruby.gemspec
77
101
  homepage: http://developer.uservoice.com
@@ -94,11 +118,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
118
  version: '0'
95
119
  requirements: []
96
120
  rubyforge_project: uservoice-ruby
97
- rubygems_version: 1.8.15
121
+ rubygems_version: 1.8.23
98
122
  signing_key:
99
123
  specification_version: 3
100
124
  summary: Client library for UserVoice API
101
125
  test_files:
102
126
  - spec/config.yml.templ
103
127
  - spec/lib/user_voice_spec.rb
128
+ - spec/lib/uservoice/client_spec.rb
129
+ - spec/lib/uservoice/collection_spec.rb
104
130
  - spec/spec_helper.rb