##// END OF EJS Templates
Test API authentification once....
Jean-Philippe Lang -
r13293:372b24627b70
parent child
Show More
@@ -1,93 +1,166
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base
21 fixtures :users
21 fixtures :users
22
22
23 def setup
23 def setup
24 Setting.rest_api_enabled = '1'
24 Setting.rest_api_enabled = '1'
25 end
25 end
26
26
27 def teardown
27 def teardown
28 Setting.rest_api_enabled = '0'
28 Setting.rest_api_enabled = '0'
29 end
29 end
30
30
31 def test_api_should_deny_without_credentials
32 get '/users/current.xml', {}
33 assert_response 401
34 assert_equal User.anonymous, User.current
35 assert response.headers.has_key?('WWW-Authenticate')
36 end
37
38 def test_api_should_accept_http_basic_auth_using_username_and_password
39 user = User.generate! do |user|
40 user.password = 'my_password'
41 end
42 get '/users/current.xml', {}, credentials(user.login, 'my_password')
43 assert_response 200
44 assert_equal user, User.current
45 end
46
47 def test_api_should_deny_http_basic_auth_using_username_and_wrong_password
48 user = User.generate! do |user|
49 user.password = 'my_password'
50 end
51 get '/users/current.xml', {}, credentials(user.login, 'wrong_password')
52 assert_response 401
53 assert_equal User.anonymous, User.current
54 end
55
56 def test_api_should_accept_http_basic_auth_using_api_key
57 user = User.generate!
58 token = Token.create!(:user => user, :action => 'api')
59 get '/users/current.xml', {}, credentials(token.value, 'X')
60 assert_response 200
61 assert_equal user, User.current
62 end
63
64 def test_api_should_deny_http_basic_auth_using_wrong_api_key
65 user = User.generate!
66 token = Token.create!(:user => user, :action => 'feeds') # not the API key
67 get '/users/current.xml', {}, credentials(token.value, 'X')
68 assert_response 401
69 assert_equal User.anonymous, User.current
70 end
71
72 def test_api_should_accept_auth_using_api_key_as_parameter
73 user = User.generate!
74 token = Token.create!(:user => user, :action => 'api')
75 get "/users/current.xml?key=#{token.value}", {}
76 assert_response 200
77 assert_equal user, User.current
78 end
79
80 def test_api_should_deny_auth_using_wrong_api_key_as_parameter
81 user = User.generate!
82 token = Token.create!(:user => user, :action => 'feeds') # not the API key
83 get "/users/current.xml?key=#{token.value}", {}
84 assert_response 401
85 assert_equal User.anonymous, User.current
86 end
87
88 def test_api_should_accept_auth_using_api_key_as_request_header
89 user = User.generate!
90 token = Token.create!(:user => user, :action => 'api')
91 get "/users/current.xml", {}, {'X-Redmine-API-Key' => token.value.to_s}
92 assert_response 200
93 assert_equal user, User.current
94 end
95
96 def test_api_should_deny_auth_using_wrong_api_key_as_request_header
97 user = User.generate!
98 token = Token.create!(:user => user, :action => 'feeds') # not the API key
99 get "/users/current.xml", {}, {'X-Redmine-API-Key' => token.value.to_s}
100 assert_response 401
101 assert_equal User.anonymous, User.current
102 end
103
31 def test_api_should_trigger_basic_http_auth_with_basic_authorization_header
104 def test_api_should_trigger_basic_http_auth_with_basic_authorization_header
32 ApplicationController.any_instance.expects(:authenticate_with_http_basic).once
105 ApplicationController.any_instance.expects(:authenticate_with_http_basic).once
33 get '/users/current.xml', {}, credentials('jsmith')
106 get '/users/current.xml', {}, credentials('jsmith')
34 assert_response 401
107 assert_response 401
35 end
108 end
36
109
37 def test_api_should_not_trigger_basic_http_auth_with_non_basic_authorization_header
110 def test_api_should_not_trigger_basic_http_auth_with_non_basic_authorization_header
38 ApplicationController.any_instance.expects(:authenticate_with_http_basic).never
111 ApplicationController.any_instance.expects(:authenticate_with_http_basic).never
39 get '/users/current.xml', {}, 'HTTP_AUTHORIZATION' => 'Digest foo bar'
112 get '/users/current.xml', {}, 'HTTP_AUTHORIZATION' => 'Digest foo bar'
40 assert_response 401
113 assert_response 401
41 end
114 end
42
115
43 def test_invalid_utf8_credentials_should_not_trigger_an_error
116 def test_invalid_utf8_credentials_should_not_trigger_an_error
44 invalid_utf8 = "\x82".force_encoding('UTF-8')
117 invalid_utf8 = "\x82".force_encoding('UTF-8')
45 assert !invalid_utf8.valid_encoding?
118 assert !invalid_utf8.valid_encoding?
46 assert_nothing_raised do
119 assert_nothing_raised do
47 get '/users/current.xml', {}, credentials(invalid_utf8, "foo")
120 get '/users/current.xml', {}, credentials(invalid_utf8, "foo")
48 end
121 end
49 end
122 end
50
123
51 def test_api_request_should_not_use_user_session
124 def test_api_request_should_not_use_user_session
52 log_user('jsmith', 'jsmith')
125 log_user('jsmith', 'jsmith')
53
126
54 get '/users/current'
127 get '/users/current'
55 assert_response :success
128 assert_response :success
56
129
57 get '/users/current.json'
130 get '/users/current.json'
58 assert_response 401
131 assert_response 401
59 end
132 end
60
133
61 def test_api_should_accept_switch_user_header_for_admin_user
134 def test_api_should_accept_switch_user_header_for_admin_user
62 user = User.find(1)
135 user = User.find(1)
63 su = User.find(4)
136 su = User.find(4)
64
137
65 get '/users/current', {}, {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login}
138 get '/users/current', {}, {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login}
66 assert_response :success
139 assert_response :success
67 assert_equal su, assigns(:user)
140 assert_equal su, assigns(:user)
68 assert_equal su, User.current
141 assert_equal su, User.current
69 end
142 end
70
143
71 def test_api_should_respond_with_412_when_trying_to_switch_to_a_invalid_user
144 def test_api_should_respond_with_412_when_trying_to_switch_to_a_invalid_user
72 get '/users/current', {}, {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => 'foobar'}
145 get '/users/current', {}, {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => 'foobar'}
73 assert_response 412
146 assert_response 412
74 end
147 end
75
148
76 def test_api_should_respond_with_412_when_trying_to_switch_to_a_locked_user
149 def test_api_should_respond_with_412_when_trying_to_switch_to_a_locked_user
77 user = User.find(5)
150 user = User.find(5)
78 assert user.locked?
151 assert user.locked?
79
152
80 get '/users/current', {}, {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => user.login}
153 get '/users/current', {}, {'X-Redmine-API-Key' => User.find(1).api_key, 'X-Redmine-Switch-User' => user.login}
81 assert_response 412
154 assert_response 412
82 end
155 end
83
156
84 def test_api_should_not_accept_switch_user_header_for_non_admin_user
157 def test_api_should_not_accept_switch_user_header_for_non_admin_user
85 user = User.find(2)
158 user = User.find(2)
86 su = User.find(4)
159 su = User.find(4)
87
160
88 get '/users/current', {}, {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login}
161 get '/users/current', {}, {'X-Redmine-API-Key' => user.api_key, 'X-Redmine-Switch-User' => su.login}
89 assert_response :success
162 assert_response :success
90 assert_equal user, assigns(:user)
163 assert_equal user, assigns(:user)
91 assert_equal user, User.current
164 assert_equal user, User.current
92 end
165 end
93 end
166 end
@@ -1,632 +1,594
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :issue_relations,
28 :issue_relations,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries,
44 :queries,
45 :attachments
45 :attachments
46
46
47 def setup
47 def setup
48 Setting.rest_api_enabled = '1'
48 Setting.rest_api_enabled = '1'
49 end
49 end
50
50
51 # Use a private project to make sure auth is really working and not just
52 # only showing public issues.
53 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
54 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
55
56 should_allow_api_authentication(:get, "/issues/6.xml")
57 should_allow_api_authentication(:get, "/issues/6.json")
58
59 should_allow_api_authentication(
60 :post,
61 '/issues.xml',
62 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
63 {:success_code => :created}
64 )
65 should_allow_api_authentication(:post,
66 '/issues.json',
67 {:issue => {:project_id => 1, :subject => 'API test',
68 :tracker_id => 2, :status_id => 3}},
69 {:success_code => :created})
70
71 should_allow_api_authentication(:put,
72 '/issues/6.xml',
73 {:issue => {:subject => 'API update', :notes => 'A new note'}},
74 {:success_code => :ok})
75 should_allow_api_authentication(:put,
76 '/issues/6.json',
77 {:issue => {:subject => 'API update', :notes => 'A new note'}},
78 {:success_code => :ok})
79
80 should_allow_api_authentication(:delete,
81 '/issues/6.xml',
82 {},
83 {:success_code => :ok})
84 should_allow_api_authentication(:delete,
85 '/issues/6.json',
86 {},
87 {:success_code => :ok})
88
89 test "GET /issues.xml should contain metadata" do
51 test "GET /issues.xml should contain metadata" do
90 get '/issues.xml'
52 get '/issues.xml'
91 assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]',
53 assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]',
92 assigns(:issue_count).to_s
54 assigns(:issue_count).to_s
93 end
55 end
94
56
95 test "GET /issues.xml with nometa param should not contain metadata" do
57 test "GET /issues.xml with nometa param should not contain metadata" do
96 get '/issues.xml?nometa=1'
58 get '/issues.xml?nometa=1'
97 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
59 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
98 end
60 end
99
61
100 test "GET /issues.xml with nometa header should not contain metadata" do
62 test "GET /issues.xml with nometa header should not contain metadata" do
101 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
63 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
102 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
64 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
103 end
65 end
104
66
105 test "GET /issues.xml with offset and limit" do
67 test "GET /issues.xml with offset and limit" do
106 get '/issues.xml?offset=2&limit=3'
68 get '/issues.xml?offset=2&limit=3'
107
69
108 assert_equal 3, assigns(:limit)
70 assert_equal 3, assigns(:limit)
109 assert_equal 2, assigns(:offset)
71 assert_equal 2, assigns(:offset)
110 assert_select 'issues issue', 3
72 assert_select 'issues issue', 3
111 end
73 end
112
74
113 test "GET /issues.xml with relations" do
75 test "GET /issues.xml with relations" do
114 get '/issues.xml?include=relations'
76 get '/issues.xml?include=relations'
115
77
116 assert_response :success
78 assert_response :success
117 assert_equal 'application/xml', @response.content_type
79 assert_equal 'application/xml', @response.content_type
118
80
119 assert_select 'issue id:content(3)' do
81 assert_select 'issue id:content(3)' do
120 assert_select '~ relations relation', 1
82 assert_select '~ relations relation', 1
121 assert_select '~ relations relation[id="2"][issue_id="2"][issue_to_id="3"][relation_type=relates]'
83 assert_select '~ relations relation[id="2"][issue_id="2"][issue_to_id="3"][relation_type=relates]'
122 end
84 end
123
85
124 assert_select 'issue id:content(1)' do
86 assert_select 'issue id:content(1)' do
125 assert_select '~ relations'
87 assert_select '~ relations'
126 assert_select '~ relations relation', 0
88 assert_select '~ relations relation', 0
127 end
89 end
128 end
90 end
129
91
130 test "GET /issues.xml with invalid query params" do
92 test "GET /issues.xml with invalid query params" do
131 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
93 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
132
94
133 assert_response :unprocessable_entity
95 assert_response :unprocessable_entity
134 assert_equal 'application/xml', @response.content_type
96 assert_equal 'application/xml', @response.content_type
135 assert_select 'errors error', :text => "Start date can't be blank"
97 assert_select 'errors error', :text => "Start date can't be blank"
136 end
98 end
137
99
138 test "GET /issues.xml with custom field filter" do
100 test "GET /issues.xml with custom field filter" do
139 get '/issues.xml',
101 get '/issues.xml',
140 {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}}
102 {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}}
141
103
142 expected_ids = Issue.visible.
104 expected_ids = Issue.visible.
143 joins(:custom_values).
105 joins(:custom_values).
144 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
106 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
145 assert expected_ids.any?
107 assert expected_ids.any?
146
108
147 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
109 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
148 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
110 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
149 end
111 end
150 end
112 end
151
113
152 test "GET /issues.xml with custom field filter (shorthand method)" do
114 test "GET /issues.xml with custom field filter (shorthand method)" do
153 get '/issues.xml', {:cf_1 => 'MySQL'}
115 get '/issues.xml', {:cf_1 => 'MySQL'}
154
116
155 expected_ids = Issue.visible.
117 expected_ids = Issue.visible.
156 joins(:custom_values).
118 joins(:custom_values).
157 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
119 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
158 assert expected_ids.any?
120 assert expected_ids.any?
159
121
160 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
122 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
161 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
123 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
162 end
124 end
163 end
125 end
164
126
165 def test_index_should_include_issue_attributes
127 def test_index_should_include_issue_attributes
166 get '/issues.xml'
128 get '/issues.xml'
167 assert_select 'issues>issue>is_private', :text => 'false'
129 assert_select 'issues>issue>is_private', :text => 'false'
168 end
130 end
169
131
170 def test_index_should_allow_timestamp_filtering
132 def test_index_should_allow_timestamp_filtering
171 Issue.delete_all
133 Issue.delete_all
172 Issue.generate!(:subject => '1').update_column(:updated_on, Time.parse("2014-01-02T10:25:00Z"))
134 Issue.generate!(:subject => '1').update_column(:updated_on, Time.parse("2014-01-02T10:25:00Z"))
173 Issue.generate!(:subject => '2').update_column(:updated_on, Time.parse("2014-01-02T12:13:00Z"))
135 Issue.generate!(:subject => '2').update_column(:updated_on, Time.parse("2014-01-02T12:13:00Z"))
174
136
175 get '/issues.xml',
137 get '/issues.xml',
176 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '<='},
138 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '<='},
177 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
139 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
178 assert_select 'issues>issue', :count => 1
140 assert_select 'issues>issue', :count => 1
179 assert_select 'issues>issue>subject', :text => '1'
141 assert_select 'issues>issue>subject', :text => '1'
180
142
181 get '/issues.xml',
143 get '/issues.xml',
182 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
144 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
183 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
145 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
184 assert_select 'issues>issue', :count => 1
146 assert_select 'issues>issue', :count => 1
185 assert_select 'issues>issue>subject', :text => '2'
147 assert_select 'issues>issue>subject', :text => '2'
186
148
187 get '/issues.xml',
149 get '/issues.xml',
188 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
150 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
189 :v => {:updated_on => ['2014-01-02T08:00:00Z']}}
151 :v => {:updated_on => ['2014-01-02T08:00:00Z']}}
190 assert_select 'issues>issue', :count => 2
152 assert_select 'issues>issue', :count => 2
191 end
153 end
192
154
193 test "GET /issues.xml with filter" do
155 test "GET /issues.xml with filter" do
194 get '/issues.xml?status_id=5'
156 get '/issues.xml?status_id=5'
195
157
196 expected_ids = Issue.visible.where(:status_id => 5).map(&:id)
158 expected_ids = Issue.visible.where(:status_id => 5).map(&:id)
197 assert expected_ids.any?
159 assert expected_ids.any?
198
160
199 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
161 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
200 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
162 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
201 end
163 end
202 end
164 end
203
165
204 test "GET /issues.json with filter" do
166 test "GET /issues.json with filter" do
205 get '/issues.json?status_id=5'
167 get '/issues.json?status_id=5'
206
168
207 json = ActiveSupport::JSON.decode(response.body)
169 json = ActiveSupport::JSON.decode(response.body)
208 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
170 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
209 assert_equal 3, status_ids_used.length
171 assert_equal 3, status_ids_used.length
210 assert status_ids_used.all? {|id| id == 5 }
172 assert status_ids_used.all? {|id| id == 5 }
211 end
173 end
212
174
213 test "GET /issues/:id.xml with journals" do
175 test "GET /issues/:id.xml with journals" do
214 get '/issues/1.xml?include=journals'
176 get '/issues/1.xml?include=journals'
215
177
216 assert_select 'issue journals[type=array]' do
178 assert_select 'issue journals[type=array]' do
217 assert_select 'journal[id="1"]' do
179 assert_select 'journal[id="1"]' do
218 assert_select 'details[type=array]' do
180 assert_select 'details[type=array]' do
219 assert_select 'detail[name=status_id]' do
181 assert_select 'detail[name=status_id]' do
220 assert_select 'old_value', :text => '1'
182 assert_select 'old_value', :text => '1'
221 assert_select 'new_value', :text => '2'
183 assert_select 'new_value', :text => '2'
222 end
184 end
223 end
185 end
224 end
186 end
225 end
187 end
226 end
188 end
227
189
228 test "GET /issues/:id.xml with custom fields" do
190 test "GET /issues/:id.xml with custom fields" do
229 get '/issues/3.xml'
191 get '/issues/3.xml'
230
192
231 assert_select 'issue custom_fields[type=array]' do
193 assert_select 'issue custom_fields[type=array]' do
232 assert_select 'custom_field[id="1"]' do
194 assert_select 'custom_field[id="1"]' do
233 assert_select 'value', :text => 'MySQL'
195 assert_select 'value', :text => 'MySQL'
234 end
196 end
235 end
197 end
236 assert_nothing_raised do
198 assert_nothing_raised do
237 Hash.from_xml(response.body).to_xml
199 Hash.from_xml(response.body).to_xml
238 end
200 end
239 end
201 end
240
202
241 test "GET /issues/:id.xml with multi custom fields" do
203 test "GET /issues/:id.xml with multi custom fields" do
242 field = CustomField.find(1)
204 field = CustomField.find(1)
243 field.update_attribute :multiple, true
205 field.update_attribute :multiple, true
244 issue = Issue.find(3)
206 issue = Issue.find(3)
245 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
207 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
246 issue.save!
208 issue.save!
247
209
248 get '/issues/3.xml'
210 get '/issues/3.xml'
249 assert_response :success
211 assert_response :success
250
212
251 assert_select 'issue custom_fields[type=array]' do
213 assert_select 'issue custom_fields[type=array]' do
252 assert_select 'custom_field[id="1"]' do
214 assert_select 'custom_field[id="1"]' do
253 assert_select 'value[type=array] value', 2
215 assert_select 'value[type=array] value', 2
254 end
216 end
255 end
217 end
256 xml = Hash.from_xml(response.body)
218 xml = Hash.from_xml(response.body)
257 custom_fields = xml['issue']['custom_fields']
219 custom_fields = xml['issue']['custom_fields']
258 assert_kind_of Array, custom_fields
220 assert_kind_of Array, custom_fields
259 field = custom_fields.detect {|f| f['id'] == '1'}
221 field = custom_fields.detect {|f| f['id'] == '1'}
260 assert_kind_of Hash, field
222 assert_kind_of Hash, field
261 assert_equal ['MySQL', 'Oracle'], field['value'].sort
223 assert_equal ['MySQL', 'Oracle'], field['value'].sort
262 end
224 end
263
225
264 test "GET /issues/:id.json with multi custom fields" do
226 test "GET /issues/:id.json with multi custom fields" do
265 field = CustomField.find(1)
227 field = CustomField.find(1)
266 field.update_attribute :multiple, true
228 field.update_attribute :multiple, true
267 issue = Issue.find(3)
229 issue = Issue.find(3)
268 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
230 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
269 issue.save!
231 issue.save!
270
232
271 get '/issues/3.json'
233 get '/issues/3.json'
272 assert_response :success
234 assert_response :success
273
235
274 json = ActiveSupport::JSON.decode(response.body)
236 json = ActiveSupport::JSON.decode(response.body)
275 custom_fields = json['issue']['custom_fields']
237 custom_fields = json['issue']['custom_fields']
276 assert_kind_of Array, custom_fields
238 assert_kind_of Array, custom_fields
277 field = custom_fields.detect {|f| f['id'] == 1}
239 field = custom_fields.detect {|f| f['id'] == 1}
278 assert_kind_of Hash, field
240 assert_kind_of Hash, field
279 assert_equal ['MySQL', 'Oracle'], field['value'].sort
241 assert_equal ['MySQL', 'Oracle'], field['value'].sort
280 end
242 end
281
243
282 test "GET /issues/:id.xml with empty value for multi custom field" do
244 test "GET /issues/:id.xml with empty value for multi custom field" do
283 field = CustomField.find(1)
245 field = CustomField.find(1)
284 field.update_attribute :multiple, true
246 field.update_attribute :multiple, true
285 issue = Issue.find(3)
247 issue = Issue.find(3)
286 issue.custom_field_values = {1 => ['']}
248 issue.custom_field_values = {1 => ['']}
287 issue.save!
249 issue.save!
288
250
289 get '/issues/3.xml'
251 get '/issues/3.xml'
290
252
291 assert_select 'issue custom_fields[type=array]' do
253 assert_select 'issue custom_fields[type=array]' do
292 assert_select 'custom_field[id="1"]' do
254 assert_select 'custom_field[id="1"]' do
293 assert_select 'value[type=array]:empty'
255 assert_select 'value[type=array]:empty'
294 end
256 end
295 end
257 end
296 xml = Hash.from_xml(response.body)
258 xml = Hash.from_xml(response.body)
297 custom_fields = xml['issue']['custom_fields']
259 custom_fields = xml['issue']['custom_fields']
298 assert_kind_of Array, custom_fields
260 assert_kind_of Array, custom_fields
299 field = custom_fields.detect {|f| f['id'] == '1'}
261 field = custom_fields.detect {|f| f['id'] == '1'}
300 assert_kind_of Hash, field
262 assert_kind_of Hash, field
301 assert_equal [], field['value']
263 assert_equal [], field['value']
302 end
264 end
303
265
304 test "GET /issues/:id.json with empty value for multi custom field" do
266 test "GET /issues/:id.json with empty value for multi custom field" do
305 field = CustomField.find(1)
267 field = CustomField.find(1)
306 field.update_attribute :multiple, true
268 field.update_attribute :multiple, true
307 issue = Issue.find(3)
269 issue = Issue.find(3)
308 issue.custom_field_values = {1 => ['']}
270 issue.custom_field_values = {1 => ['']}
309 issue.save!
271 issue.save!
310
272
311 get '/issues/3.json'
273 get '/issues/3.json'
312 assert_response :success
274 assert_response :success
313 json = ActiveSupport::JSON.decode(response.body)
275 json = ActiveSupport::JSON.decode(response.body)
314 custom_fields = json['issue']['custom_fields']
276 custom_fields = json['issue']['custom_fields']
315 assert_kind_of Array, custom_fields
277 assert_kind_of Array, custom_fields
316 field = custom_fields.detect {|f| f['id'] == 1}
278 field = custom_fields.detect {|f| f['id'] == 1}
317 assert_kind_of Hash, field
279 assert_kind_of Hash, field
318 assert_equal [], field['value'].sort
280 assert_equal [], field['value'].sort
319 end
281 end
320
282
321 test "GET /issues/:id.xml with attachments" do
283 test "GET /issues/:id.xml with attachments" do
322 get '/issues/3.xml?include=attachments'
284 get '/issues/3.xml?include=attachments'
323
285
324 assert_select 'issue attachments[type=array]' do
286 assert_select 'issue attachments[type=array]' do
325 assert_select 'attachment', 5
287 assert_select 'attachment', 5
326 assert_select 'attachment id:content(4)' do
288 assert_select 'attachment id:content(4)' do
327 assert_select '~ filename', :text => 'source.rb'
289 assert_select '~ filename', :text => 'source.rb'
328 assert_select '~ content_url', :text => 'http://www.example.com/attachments/download/4/source.rb'
290 assert_select '~ content_url', :text => 'http://www.example.com/attachments/download/4/source.rb'
329 end
291 end
330 end
292 end
331 end
293 end
332
294
333 test "GET /issues/:id.xml with subtasks" do
295 test "GET /issues/:id.xml with subtasks" do
334 issue = Issue.generate_with_descendants!(:project_id => 1)
296 issue = Issue.generate_with_descendants!(:project_id => 1)
335 get "/issues/#{issue.id}.xml?include=children"
297 get "/issues/#{issue.id}.xml?include=children"
336
298
337 assert_select 'issue children[type=array]' do
299 assert_select 'issue children[type=array]' do
338 assert_select 'issue', 2
300 assert_select 'issue', 2
339 assert_select 'issue children', 1
301 assert_select 'issue children', 1
340 end
302 end
341 end
303 end
342
304
343 test "GET /issues/:id.json with subtasks" do
305 test "GET /issues/:id.json with subtasks" do
344 issue = Issue.generate_with_descendants!(:project_id => 1)
306 issue = Issue.generate_with_descendants!(:project_id => 1)
345 get "/issues/#{issue.id}.json?include=children"
307 get "/issues/#{issue.id}.json?include=children"
346
308
347 json = ActiveSupport::JSON.decode(response.body)
309 json = ActiveSupport::JSON.decode(response.body)
348 assert_equal 2, json['issue']['children'].size
310 assert_equal 2, json['issue']['children'].size
349 assert_equal 1, json['issue']['children'].select {|child| child.key?('children')}.size
311 assert_equal 1, json['issue']['children'].select {|child| child.key?('children')}.size
350 end
312 end
351
313
352 def test_show_should_include_issue_attributes
314 def test_show_should_include_issue_attributes
353 get '/issues/1.xml'
315 get '/issues/1.xml'
354 assert_select 'issue>is_private', :text => 'false'
316 assert_select 'issue>is_private', :text => 'false'
355 end
317 end
356
318
357 test "GET /issues/:id.xml?include=watchers should include watchers" do
319 test "GET /issues/:id.xml?include=watchers should include watchers" do
358 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
320 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
359
321
360 get '/issues/1.xml?include=watchers', {}, credentials('jsmith')
322 get '/issues/1.xml?include=watchers', {}, credentials('jsmith')
361
323
362 assert_response :ok
324 assert_response :ok
363 assert_equal 'application/xml', response.content_type
325 assert_equal 'application/xml', response.content_type
364 assert_select 'issue' do
326 assert_select 'issue' do
365 assert_select 'watchers', Issue.find(1).watchers.count
327 assert_select 'watchers', Issue.find(1).watchers.count
366 assert_select 'watchers' do
328 assert_select 'watchers' do
367 assert_select 'user[id=3]'
329 assert_select 'user[id=3]'
368 end
330 end
369 end
331 end
370 end
332 end
371
333
372 test "POST /issues.xml should create an issue with the attributes" do
334 test "POST /issues.xml should create an issue with the attributes" do
373 assert_difference('Issue.count') do
335 assert_difference('Issue.count') do
374 post '/issues.xml',
336 post '/issues.xml',
375 {:issue => {:project_id => 1, :subject => 'API test',
337 {:issue => {:project_id => 1, :subject => 'API test',
376 :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
338 :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
377 end
339 end
378 issue = Issue.order('id DESC').first
340 issue = Issue.order('id DESC').first
379 assert_equal 1, issue.project_id
341 assert_equal 1, issue.project_id
380 assert_equal 2, issue.tracker_id
342 assert_equal 2, issue.tracker_id
381 assert_equal 3, issue.status_id
343 assert_equal 3, issue.status_id
382 assert_equal 'API test', issue.subject
344 assert_equal 'API test', issue.subject
383
345
384 assert_response :created
346 assert_response :created
385 assert_equal 'application/xml', @response.content_type
347 assert_equal 'application/xml', @response.content_type
386 assert_select 'issue > id', :text => issue.id.to_s
348 assert_select 'issue > id', :text => issue.id.to_s
387 end
349 end
388
350
389 test "POST /issues.xml with watcher_user_ids should create issue with watchers" do
351 test "POST /issues.xml with watcher_user_ids should create issue with watchers" do
390 assert_difference('Issue.count') do
352 assert_difference('Issue.count') do
391 post '/issues.xml',
353 post '/issues.xml',
392 {:issue => {:project_id => 1, :subject => 'Watchers',
354 {:issue => {:project_id => 1, :subject => 'Watchers',
393 :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith')
355 :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith')
394 assert_response :created
356 assert_response :created
395 end
357 end
396 issue = Issue.order('id desc').first
358 issue = Issue.order('id desc').first
397 assert_equal 2, issue.watchers.size
359 assert_equal 2, issue.watchers.size
398 assert_equal [1, 3], issue.watcher_user_ids.sort
360 assert_equal [1, 3], issue.watcher_user_ids.sort
399 end
361 end
400
362
401 test "POST /issues.xml with failure should return errors" do
363 test "POST /issues.xml with failure should return errors" do
402 assert_no_difference('Issue.count') do
364 assert_no_difference('Issue.count') do
403 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
365 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
404 end
366 end
405
367
406 assert_select 'errors error', :text => "Subject can't be blank"
368 assert_select 'errors error', :text => "Subject can't be blank"
407 end
369 end
408
370
409 test "POST /issues.json should create an issue with the attributes" do
371 test "POST /issues.json should create an issue with the attributes" do
410 assert_difference('Issue.count') do
372 assert_difference('Issue.count') do
411 post '/issues.json',
373 post '/issues.json',
412 {:issue => {:project_id => 1, :subject => 'API test',
374 {:issue => {:project_id => 1, :subject => 'API test',
413 :tracker_id => 2, :status_id => 3}},
375 :tracker_id => 2, :status_id => 3}},
414 credentials('jsmith')
376 credentials('jsmith')
415 end
377 end
416
378
417 issue = Issue.order('id DESC').first
379 issue = Issue.order('id DESC').first
418 assert_equal 1, issue.project_id
380 assert_equal 1, issue.project_id
419 assert_equal 2, issue.tracker_id
381 assert_equal 2, issue.tracker_id
420 assert_equal 3, issue.status_id
382 assert_equal 3, issue.status_id
421 assert_equal 'API test', issue.subject
383 assert_equal 'API test', issue.subject
422 end
384 end
423
385
424 test "POST /issues.json with failure should return errors" do
386 test "POST /issues.json with failure should return errors" do
425 assert_no_difference('Issue.count') do
387 assert_no_difference('Issue.count') do
426 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
388 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
427 end
389 end
428
390
429 json = ActiveSupport::JSON.decode(response.body)
391 json = ActiveSupport::JSON.decode(response.body)
430 assert json['errors'].include?("Subject can't be blank")
392 assert json['errors'].include?("Subject can't be blank")
431 end
393 end
432
394
433 test "PUT /issues/:id.xml" do
395 test "PUT /issues/:id.xml" do
434 assert_difference('Journal.count') do
396 assert_difference('Journal.count') do
435 put '/issues/6.xml',
397 put '/issues/6.xml',
436 {:issue => {:subject => 'API update', :notes => 'A new note'}},
398 {:issue => {:subject => 'API update', :notes => 'A new note'}},
437 credentials('jsmith')
399 credentials('jsmith')
438 end
400 end
439
401
440 issue = Issue.find(6)
402 issue = Issue.find(6)
441 assert_equal "API update", issue.subject
403 assert_equal "API update", issue.subject
442 journal = Journal.last
404 journal = Journal.last
443 assert_equal "A new note", journal.notes
405 assert_equal "A new note", journal.notes
444 end
406 end
445
407
446 test "PUT /issues/:id.xml with custom fields" do
408 test "PUT /issues/:id.xml with custom fields" do
447 put '/issues/3.xml',
409 put '/issues/3.xml',
448 {:issue => {:custom_fields => [
410 {:issue => {:custom_fields => [
449 {'id' => '1', 'value' => 'PostgreSQL' },
411 {'id' => '1', 'value' => 'PostgreSQL' },
450 {'id' => '2', 'value' => '150'}
412 {'id' => '2', 'value' => '150'}
451 ]}},
413 ]}},
452 credentials('jsmith')
414 credentials('jsmith')
453
415
454 issue = Issue.find(3)
416 issue = Issue.find(3)
455 assert_equal '150', issue.custom_value_for(2).value
417 assert_equal '150', issue.custom_value_for(2).value
456 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
418 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
457 end
419 end
458
420
459 test "PUT /issues/:id.xml with multi custom fields" do
421 test "PUT /issues/:id.xml with multi custom fields" do
460 field = CustomField.find(1)
422 field = CustomField.find(1)
461 field.update_attribute :multiple, true
423 field.update_attribute :multiple, true
462
424
463 put '/issues/3.xml',
425 put '/issues/3.xml',
464 {:issue => {:custom_fields => [
426 {:issue => {:custom_fields => [
465 {'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] },
427 {'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] },
466 {'id' => '2', 'value' => '150'}
428 {'id' => '2', 'value' => '150'}
467 ]}},
429 ]}},
468 credentials('jsmith')
430 credentials('jsmith')
469
431
470 issue = Issue.find(3)
432 issue = Issue.find(3)
471 assert_equal '150', issue.custom_value_for(2).value
433 assert_equal '150', issue.custom_value_for(2).value
472 assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort
434 assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort
473 end
435 end
474
436
475 test "PUT /issues/:id.xml with project change" do
437 test "PUT /issues/:id.xml with project change" do
476 put '/issues/3.xml',
438 put '/issues/3.xml',
477 {:issue => {:project_id => 2, :subject => 'Project changed'}},
439 {:issue => {:project_id => 2, :subject => 'Project changed'}},
478 credentials('jsmith')
440 credentials('jsmith')
479
441
480 issue = Issue.find(3)
442 issue = Issue.find(3)
481 assert_equal 2, issue.project_id
443 assert_equal 2, issue.project_id
482 assert_equal 'Project changed', issue.subject
444 assert_equal 'Project changed', issue.subject
483 end
445 end
484
446
485 test "PUT /issues/:id.xml with failed update" do
447 test "PUT /issues/:id.xml with failed update" do
486 put '/issues/6.xml', {:issue => {:subject => ''}}, credentials('jsmith')
448 put '/issues/6.xml', {:issue => {:subject => ''}}, credentials('jsmith')
487
449
488 assert_response :unprocessable_entity
450 assert_response :unprocessable_entity
489 assert_select 'errors error', :text => "Subject can't be blank"
451 assert_select 'errors error', :text => "Subject can't be blank"
490 end
452 end
491
453
492 test "PUT /issues/:id.json" do
454 test "PUT /issues/:id.json" do
493 assert_difference('Journal.count') do
455 assert_difference('Journal.count') do
494 put '/issues/6.json',
456 put '/issues/6.json',
495 {:issue => {:subject => 'API update', :notes => 'A new note'}},
457 {:issue => {:subject => 'API update', :notes => 'A new note'}},
496 credentials('jsmith')
458 credentials('jsmith')
497
459
498 assert_response :ok
460 assert_response :ok
499 assert_equal '', response.body
461 assert_equal '', response.body
500 end
462 end
501
463
502 issue = Issue.find(6)
464 issue = Issue.find(6)
503 assert_equal "API update", issue.subject
465 assert_equal "API update", issue.subject
504 journal = Journal.last
466 journal = Journal.last
505 assert_equal "A new note", journal.notes
467 assert_equal "A new note", journal.notes
506 end
468 end
507
469
508 test "PUT /issues/:id.json with failed update" do
470 test "PUT /issues/:id.json with failed update" do
509 put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith')
471 put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith')
510
472
511 assert_response :unprocessable_entity
473 assert_response :unprocessable_entity
512 json = ActiveSupport::JSON.decode(response.body)
474 json = ActiveSupport::JSON.decode(response.body)
513 assert json['errors'].include?("Subject can't be blank")
475 assert json['errors'].include?("Subject can't be blank")
514 end
476 end
515
477
516 test "DELETE /issues/:id.xml" do
478 test "DELETE /issues/:id.xml" do
517 assert_difference('Issue.count', -1) do
479 assert_difference('Issue.count', -1) do
518 delete '/issues/6.xml', {}, credentials('jsmith')
480 delete '/issues/6.xml', {}, credentials('jsmith')
519
481
520 assert_response :ok
482 assert_response :ok
521 assert_equal '', response.body
483 assert_equal '', response.body
522 end
484 end
523 assert_nil Issue.find_by_id(6)
485 assert_nil Issue.find_by_id(6)
524 end
486 end
525
487
526 test "DELETE /issues/:id.json" do
488 test "DELETE /issues/:id.json" do
527 assert_difference('Issue.count', -1) do
489 assert_difference('Issue.count', -1) do
528 delete '/issues/6.json', {}, credentials('jsmith')
490 delete '/issues/6.json', {}, credentials('jsmith')
529
491
530 assert_response :ok
492 assert_response :ok
531 assert_equal '', response.body
493 assert_equal '', response.body
532 end
494 end
533 assert_nil Issue.find_by_id(6)
495 assert_nil Issue.find_by_id(6)
534 end
496 end
535
497
536 test "POST /issues/:id/watchers.xml should add watcher" do
498 test "POST /issues/:id/watchers.xml should add watcher" do
537 assert_difference 'Watcher.count' do
499 assert_difference 'Watcher.count' do
538 post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith')
500 post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith')
539
501
540 assert_response :ok
502 assert_response :ok
541 assert_equal '', response.body
503 assert_equal '', response.body
542 end
504 end
543 watcher = Watcher.order('id desc').first
505 watcher = Watcher.order('id desc').first
544 assert_equal Issue.find(1), watcher.watchable
506 assert_equal Issue.find(1), watcher.watchable
545 assert_equal User.find(3), watcher.user
507 assert_equal User.find(3), watcher.user
546 end
508 end
547
509
548 test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do
510 test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do
549 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
511 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
550
512
551 assert_difference 'Watcher.count', -1 do
513 assert_difference 'Watcher.count', -1 do
552 delete '/issues/1/watchers/3.xml', {}, credentials('jsmith')
514 delete '/issues/1/watchers/3.xml', {}, credentials('jsmith')
553
515
554 assert_response :ok
516 assert_response :ok
555 assert_equal '', response.body
517 assert_equal '', response.body
556 end
518 end
557 assert_equal false, Issue.find(1).watched_by?(User.find(3))
519 assert_equal false, Issue.find(1).watched_by?(User.find(3))
558 end
520 end
559
521
560 def test_create_issue_with_uploaded_file
522 def test_create_issue_with_uploaded_file
561 set_tmp_attachments_directory
523 set_tmp_attachments_directory
562 # upload the file
524 # upload the file
563 assert_difference 'Attachment.count' do
525 assert_difference 'Attachment.count' do
564 post '/uploads.xml', 'test_create_with_upload',
526 post '/uploads.xml', 'test_create_with_upload',
565 {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
527 {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
566 assert_response :created
528 assert_response :created
567 end
529 end
568 xml = Hash.from_xml(response.body)
530 xml = Hash.from_xml(response.body)
569 token = xml['upload']['token']
531 token = xml['upload']['token']
570 attachment = Attachment.order('id DESC').first
532 attachment = Attachment.order('id DESC').first
571
533
572 # create the issue with the upload's token
534 # create the issue with the upload's token
573 assert_difference 'Issue.count' do
535 assert_difference 'Issue.count' do
574 post '/issues.xml',
536 post '/issues.xml',
575 {:issue => {:project_id => 1, :subject => 'Uploaded file',
537 {:issue => {:project_id => 1, :subject => 'Uploaded file',
576 :uploads => [{:token => token, :filename => 'test.txt',
538 :uploads => [{:token => token, :filename => 'test.txt',
577 :content_type => 'text/plain'}]}},
539 :content_type => 'text/plain'}]}},
578 credentials('jsmith')
540 credentials('jsmith')
579 assert_response :created
541 assert_response :created
580 end
542 end
581 issue = Issue.order('id DESC').first
543 issue = Issue.order('id DESC').first
582 assert_equal 1, issue.attachments.count
544 assert_equal 1, issue.attachments.count
583 assert_equal attachment, issue.attachments.first
545 assert_equal attachment, issue.attachments.first
584
546
585 attachment.reload
547 attachment.reload
586 assert_equal 'test.txt', attachment.filename
548 assert_equal 'test.txt', attachment.filename
587 assert_equal 'text/plain', attachment.content_type
549 assert_equal 'text/plain', attachment.content_type
588 assert_equal 'test_create_with_upload'.size, attachment.filesize
550 assert_equal 'test_create_with_upload'.size, attachment.filesize
589 assert_equal 2, attachment.author_id
551 assert_equal 2, attachment.author_id
590
552
591 # get the issue with its attachments
553 # get the issue with its attachments
592 get "/issues/#{issue.id}.xml", :include => 'attachments'
554 get "/issues/#{issue.id}.xml", :include => 'attachments'
593 assert_response :success
555 assert_response :success
594 xml = Hash.from_xml(response.body)
556 xml = Hash.from_xml(response.body)
595 attachments = xml['issue']['attachments']
557 attachments = xml['issue']['attachments']
596 assert_kind_of Array, attachments
558 assert_kind_of Array, attachments
597 assert_equal 1, attachments.size
559 assert_equal 1, attachments.size
598 url = attachments.first['content_url']
560 url = attachments.first['content_url']
599 assert_not_nil url
561 assert_not_nil url
600
562
601 # download the attachment
563 # download the attachment
602 get url
564 get url
603 assert_response :success
565 assert_response :success
604 end
566 end
605
567
606 def test_update_issue_with_uploaded_file
568 def test_update_issue_with_uploaded_file
607 set_tmp_attachments_directory
569 set_tmp_attachments_directory
608 # upload the file
570 # upload the file
609 assert_difference 'Attachment.count' do
571 assert_difference 'Attachment.count' do
610 post '/uploads.xml', 'test_upload_with_upload',
572 post '/uploads.xml', 'test_upload_with_upload',
611 {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
573 {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
612 assert_response :created
574 assert_response :created
613 end
575 end
614 xml = Hash.from_xml(response.body)
576 xml = Hash.from_xml(response.body)
615 token = xml['upload']['token']
577 token = xml['upload']['token']
616 attachment = Attachment.order('id DESC').first
578 attachment = Attachment.order('id DESC').first
617
579
618 # update the issue with the upload's token
580 # update the issue with the upload's token
619 assert_difference 'Journal.count' do
581 assert_difference 'Journal.count' do
620 put '/issues/1.xml',
582 put '/issues/1.xml',
621 {:issue => {:notes => 'Attachment added',
583 {:issue => {:notes => 'Attachment added',
622 :uploads => [{:token => token, :filename => 'test.txt',
584 :uploads => [{:token => token, :filename => 'test.txt',
623 :content_type => 'text/plain'}]}},
585 :content_type => 'text/plain'}]}},
624 credentials('jsmith')
586 credentials('jsmith')
625 assert_response :ok
587 assert_response :ok
626 assert_equal '', @response.body
588 assert_equal '', @response.body
627 end
589 end
628
590
629 issue = Issue.find(1)
591 issue = Issue.find(1)
630 assert_include attachment, issue.attachments
592 assert_include attachment, issue.attachments
631 end
593 end
632 end
594 end
@@ -1,68 +1,65
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base
21 fixtures :projects, :trackers, :issue_statuses, :issues,
21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 :enumerations, :users, :issue_categories,
22 :enumerations, :users, :issue_categories,
23 :projects_trackers,
23 :projects_trackers,
24 :roles,
24 :roles,
25 :member_roles,
25 :member_roles,
26 :members,
26 :members,
27 :enabled_modules,
27 :enabled_modules,
28 :news
28 :news
29
29
30 def setup
30 def setup
31 Setting.rest_api_enabled = '1'
31 Setting.rest_api_enabled = '1'
32 end
32 end
33
33
34 should_allow_api_authentication(:get, "/projects/onlinestore/news.xml")
35 should_allow_api_authentication(:get, "/projects/onlinestore/news.json")
36
37 test "GET /news.xml should return news" do
34 test "GET /news.xml should return news" do
38 get '/news.xml'
35 get '/news.xml'
39
36
40 assert_select 'news[type=array] news id', :text => '2'
37 assert_select 'news[type=array] news id', :text => '2'
41 end
38 end
42
39
43 test "GET /news.json should return news" do
40 test "GET /news.json should return news" do
44 get '/news.json'
41 get '/news.json'
45
42
46 json = ActiveSupport::JSON.decode(response.body)
43 json = ActiveSupport::JSON.decode(response.body)
47 assert_kind_of Hash, json
44 assert_kind_of Hash, json
48 assert_kind_of Array, json['news']
45 assert_kind_of Array, json['news']
49 assert_kind_of Hash, json['news'].first
46 assert_kind_of Hash, json['news'].first
50 assert_equal 2, json['news'].first['id']
47 assert_equal 2, json['news'].first['id']
51 end
48 end
52
49
53 test "GET /projects/:project_id/news.xml should return news" do
50 test "GET /projects/:project_id/news.xml should return news" do
54 get '/projects/ecookbook/news.xml'
51 get '/projects/ecookbook/news.xml'
55
52
56 assert_select 'news[type=array] news id', :text => '2'
53 assert_select 'news[type=array] news id', :text => '2'
57 end
54 end
58
55
59 test "GET /projects/:project_id/news.json should return news" do
56 test "GET /projects/:project_id/news.json should return news" do
60 get '/projects/ecookbook/news.json'
57 get '/projects/ecookbook/news.json'
61
58
62 json = ActiveSupport::JSON.decode(response.body)
59 json = ActiveSupport::JSON.decode(response.body)
63 assert_kind_of Hash, json
60 assert_kind_of Hash, json
64 assert_kind_of Array, json['news']
61 assert_kind_of Array, json['news']
65 assert_kind_of Hash, json['news'].first
62 assert_kind_of Hash, json['news'].first
66 assert_equal 2, json['news'].first['id']
63 assert_equal 2, json['news'].first['id']
67 end
64 end
68 end
65 end
@@ -1,251 +1,234
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base
21 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
21 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
23 :attachments, :custom_fields, :custom_values, :time_entries, :issue_categories
23 :attachments, :custom_fields, :custom_values, :time_entries, :issue_categories
24
24
25 def setup
25 def setup
26 Setting.rest_api_enabled = '1'
26 Setting.rest_api_enabled = '1'
27 set_tmp_attachments_directory
27 set_tmp_attachments_directory
28 end
28 end
29
29
30 # TODO: A private project is needed because should_allow_api_authentication
31 # actually tests that authentication is *required*, not just allowed
32 should_allow_api_authentication(:get, "/projects/2.xml")
33 should_allow_api_authentication(:get, "/projects/2.json")
34 should_allow_api_authentication(:post,
35 '/projects.xml',
36 {:project => {:name => 'API test', :identifier => 'api-test'}},
37 {:success_code => :created})
38 should_allow_api_authentication(:put,
39 '/projects/2.xml',
40 {:project => {:name => 'API update'}},
41 {:success_code => :ok})
42 should_allow_api_authentication(:delete,
43 '/projects/2.xml',
44 {},
45 {:success_code => :ok})
46
47 test "GET /projects.xml should return projects" do
30 test "GET /projects.xml should return projects" do
48 get '/projects.xml'
31 get '/projects.xml'
49 assert_response :success
32 assert_response :success
50 assert_equal 'application/xml', @response.content_type
33 assert_equal 'application/xml', @response.content_type
51
34
52 assert_select 'projects>project>id', :text => '1'
35 assert_select 'projects>project>id', :text => '1'
53 assert_select 'projects>project>status', :text => '1'
36 assert_select 'projects>project>status', :text => '1'
54 assert_select 'projects>project>is_public', :text => 'true'
37 assert_select 'projects>project>is_public', :text => 'true'
55 end
38 end
56
39
57 test "GET /projects.json should return projects" do
40 test "GET /projects.json should return projects" do
58 get '/projects.json'
41 get '/projects.json'
59 assert_response :success
42 assert_response :success
60 assert_equal 'application/json', @response.content_type
43 assert_equal 'application/json', @response.content_type
61
44
62 json = ActiveSupport::JSON.decode(response.body)
45 json = ActiveSupport::JSON.decode(response.body)
63 assert_kind_of Hash, json
46 assert_kind_of Hash, json
64 assert_kind_of Array, json['projects']
47 assert_kind_of Array, json['projects']
65 assert_kind_of Hash, json['projects'].first
48 assert_kind_of Hash, json['projects'].first
66 assert json['projects'].first.has_key?('id')
49 assert json['projects'].first.has_key?('id')
67 end
50 end
68
51
69 test "GET /projects.xml with include=issue_categories should return categories" do
52 test "GET /projects.xml with include=issue_categories should return categories" do
70 get '/projects.xml?include=issue_categories'
53 get '/projects.xml?include=issue_categories'
71 assert_response :success
54 assert_response :success
72 assert_equal 'application/xml', @response.content_type
55 assert_equal 'application/xml', @response.content_type
73
56
74 assert_select 'issue_categories[type=array] issue_category[id=2][name=Recipes]'
57 assert_select 'issue_categories[type=array] issue_category[id=2][name=Recipes]'
75 end
58 end
76
59
77 test "GET /projects.xml with include=trackers should return trackers" do
60 test "GET /projects.xml with include=trackers should return trackers" do
78 get '/projects.xml?include=trackers'
61 get '/projects.xml?include=trackers'
79 assert_response :success
62 assert_response :success
80 assert_equal 'application/xml', @response.content_type
63 assert_equal 'application/xml', @response.content_type
81
64
82 assert_select 'trackers[type=array] tracker[id=2][name=Feature request]'
65 assert_select 'trackers[type=array] tracker[id=2][name=Feature request]'
83 end
66 end
84
67
85 test "GET /projects.xml with include=enabled_modules should return enabled modules" do
68 test "GET /projects.xml with include=enabled_modules should return enabled modules" do
86 get '/projects.xml?include=enabled_modules'
69 get '/projects.xml?include=enabled_modules'
87 assert_response :success
70 assert_response :success
88 assert_equal 'application/xml', @response.content_type
71 assert_equal 'application/xml', @response.content_type
89
72
90 assert_select 'enabled_modules[type=array] enabled_module[name=issue_tracking]'
73 assert_select 'enabled_modules[type=array] enabled_module[name=issue_tracking]'
91 end
74 end
92
75
93 test "GET /projects/:id.xml should return the project" do
76 test "GET /projects/:id.xml should return the project" do
94 get '/projects/1.xml'
77 get '/projects/1.xml'
95 assert_response :success
78 assert_response :success
96 assert_equal 'application/xml', @response.content_type
79 assert_equal 'application/xml', @response.content_type
97
80
98 assert_select 'project>id', :text => '1'
81 assert_select 'project>id', :text => '1'
99 assert_select 'project>status', :text => '1'
82 assert_select 'project>status', :text => '1'
100 assert_select 'project>is_public', :text => 'true'
83 assert_select 'project>is_public', :text => 'true'
101 assert_select 'custom_field[name=Development status]', :text => 'Stable'
84 assert_select 'custom_field[name=Development status]', :text => 'Stable'
102
85
103 assert_select 'trackers', 0
86 assert_select 'trackers', 0
104 assert_select 'issue_categories', 0
87 assert_select 'issue_categories', 0
105 end
88 end
106
89
107 test "GET /projects/:id.json should return the project" do
90 test "GET /projects/:id.json should return the project" do
108 get '/projects/1.json'
91 get '/projects/1.json'
109
92
110 json = ActiveSupport::JSON.decode(response.body)
93 json = ActiveSupport::JSON.decode(response.body)
111 assert_kind_of Hash, json
94 assert_kind_of Hash, json
112 assert_kind_of Hash, json['project']
95 assert_kind_of Hash, json['project']
113 assert_equal 1, json['project']['id']
96 assert_equal 1, json['project']['id']
114 end
97 end
115
98
116 test "GET /projects/:id.xml with hidden custom fields should not display hidden custom fields" do
99 test "GET /projects/:id.xml with hidden custom fields should not display hidden custom fields" do
117 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
100 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
118
101
119 get '/projects/1.xml'
102 get '/projects/1.xml'
120 assert_response :success
103 assert_response :success
121 assert_equal 'application/xml', @response.content_type
104 assert_equal 'application/xml', @response.content_type
122
105
123 assert_select 'custom_field[name=?]', 'Development status', 0
106 assert_select 'custom_field[name=?]', 'Development status', 0
124 end
107 end
125
108
126 test "GET /projects/:id.xml with include=issue_categories should return categories" do
109 test "GET /projects/:id.xml with include=issue_categories should return categories" do
127 get '/projects/1.xml?include=issue_categories'
110 get '/projects/1.xml?include=issue_categories'
128 assert_response :success
111 assert_response :success
129 assert_equal 'application/xml', @response.content_type
112 assert_equal 'application/xml', @response.content_type
130
113
131 assert_select 'issue_categories[type=array] issue_category[id=2][name=Recipes]'
114 assert_select 'issue_categories[type=array] issue_category[id=2][name=Recipes]'
132 end
115 end
133
116
134 test "GET /projects/:id.xml with include=trackers should return trackers" do
117 test "GET /projects/:id.xml with include=trackers should return trackers" do
135 get '/projects/1.xml?include=trackers'
118 get '/projects/1.xml?include=trackers'
136 assert_response :success
119 assert_response :success
137 assert_equal 'application/xml', @response.content_type
120 assert_equal 'application/xml', @response.content_type
138
121
139 assert_select 'trackers[type=array] tracker[id=2][name=Feature request]'
122 assert_select 'trackers[type=array] tracker[id=2][name=Feature request]'
140 end
123 end
141
124
142 test "GET /projects/:id.xml with include=enabled_modules should return enabled modules" do
125 test "GET /projects/:id.xml with include=enabled_modules should return enabled modules" do
143 get '/projects/1.xml?include=enabled_modules'
126 get '/projects/1.xml?include=enabled_modules'
144 assert_response :success
127 assert_response :success
145 assert_equal 'application/xml', @response.content_type
128 assert_equal 'application/xml', @response.content_type
146
129
147 assert_select 'enabled_modules[type=array] enabled_module[name=issue_tracking]'
130 assert_select 'enabled_modules[type=array] enabled_module[name=issue_tracking]'
148 end
131 end
149
132
150 test "POST /projects.xml with valid parameters should create the project" do
133 test "POST /projects.xml with valid parameters should create the project" do
151 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
134 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
152 assert_difference('Project.count') do
135 assert_difference('Project.count') do
153 post '/projects.xml',
136 post '/projects.xml',
154 {:project => {:name => 'API test', :identifier => 'api-test'}},
137 {:project => {:name => 'API test', :identifier => 'api-test'}},
155 credentials('admin')
138 credentials('admin')
156 end
139 end
157 end
140 end
158
141
159 project = Project.order('id DESC').first
142 project = Project.order('id DESC').first
160 assert_equal 'API test', project.name
143 assert_equal 'API test', project.name
161 assert_equal 'api-test', project.identifier
144 assert_equal 'api-test', project.identifier
162 assert_equal ['issue_tracking', 'repository'], project.enabled_module_names.sort
145 assert_equal ['issue_tracking', 'repository'], project.enabled_module_names.sort
163 assert_equal Tracker.all.size, project.trackers.size
146 assert_equal Tracker.all.size, project.trackers.size
164
147
165 assert_response :created
148 assert_response :created
166 assert_equal 'application/xml', @response.content_type
149 assert_equal 'application/xml', @response.content_type
167 assert_select 'project id', :text => project.id.to_s
150 assert_select 'project id', :text => project.id.to_s
168 end
151 end
169
152
170 test "POST /projects.xml should accept enabled_module_names attribute" do
153 test "POST /projects.xml should accept enabled_module_names attribute" do
171 assert_difference('Project.count') do
154 assert_difference('Project.count') do
172 post '/projects.xml',
155 post '/projects.xml',
173 {:project => {:name => 'API test', :identifier => 'api-test', :enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}},
156 {:project => {:name => 'API test', :identifier => 'api-test', :enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}},
174 credentials('admin')
157 credentials('admin')
175 end
158 end
176
159
177 project = Project.order('id DESC').first
160 project = Project.order('id DESC').first
178 assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort
161 assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort
179 end
162 end
180
163
181 test "POST /projects.xml should accept tracker_ids attribute" do
164 test "POST /projects.xml should accept tracker_ids attribute" do
182 assert_difference('Project.count') do
165 assert_difference('Project.count') do
183 post '/projects.xml',
166 post '/projects.xml',
184 {:project => {:name => 'API test', :identifier => 'api-test', :tracker_ids => [1, 3]}},
167 {:project => {:name => 'API test', :identifier => 'api-test', :tracker_ids => [1, 3]}},
185 credentials('admin')
168 credentials('admin')
186 end
169 end
187
170
188 project = Project.order('id DESC').first
171 project = Project.order('id DESC').first
189 assert_equal [1, 3], project.trackers.map(&:id).sort
172 assert_equal [1, 3], project.trackers.map(&:id).sort
190 end
173 end
191
174
192 test "POST /projects.xml with invalid parameters should return errors" do
175 test "POST /projects.xml with invalid parameters should return errors" do
193 assert_no_difference('Project.count') do
176 assert_no_difference('Project.count') do
194 post '/projects.xml', {:project => {:name => 'API test'}}, credentials('admin')
177 post '/projects.xml', {:project => {:name => 'API test'}}, credentials('admin')
195 end
178 end
196
179
197 assert_response :unprocessable_entity
180 assert_response :unprocessable_entity
198 assert_equal 'application/xml', @response.content_type
181 assert_equal 'application/xml', @response.content_type
199 assert_select 'errors error', :text => "Identifier can't be blank"
182 assert_select 'errors error', :text => "Identifier can't be blank"
200 end
183 end
201
184
202 test "PUT /projects/:id.xml with valid parameters should update the project" do
185 test "PUT /projects/:id.xml with valid parameters should update the project" do
203 assert_no_difference 'Project.count' do
186 assert_no_difference 'Project.count' do
204 put '/projects/2.xml', {:project => {:name => 'API update'}}, credentials('jsmith')
187 put '/projects/2.xml', {:project => {:name => 'API update'}}, credentials('jsmith')
205 end
188 end
206 assert_response :ok
189 assert_response :ok
207 assert_equal '', @response.body
190 assert_equal '', @response.body
208 assert_equal 'application/xml', @response.content_type
191 assert_equal 'application/xml', @response.content_type
209 project = Project.find(2)
192 project = Project.find(2)
210 assert_equal 'API update', project.name
193 assert_equal 'API update', project.name
211 end
194 end
212
195
213 test "PUT /projects/:id.xml should accept enabled_module_names attribute" do
196 test "PUT /projects/:id.xml should accept enabled_module_names attribute" do
214 assert_no_difference 'Project.count' do
197 assert_no_difference 'Project.count' do
215 put '/projects/2.xml', {:project => {:name => 'API update', :enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}}, credentials('admin')
198 put '/projects/2.xml', {:project => {:name => 'API update', :enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}}, credentials('admin')
216 end
199 end
217 assert_response :ok
200 assert_response :ok
218 assert_equal '', @response.body
201 assert_equal '', @response.body
219 project = Project.find(2)
202 project = Project.find(2)
220 assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort
203 assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort
221 end
204 end
222
205
223 test "PUT /projects/:id.xml should accept tracker_ids attribute" do
206 test "PUT /projects/:id.xml should accept tracker_ids attribute" do
224 assert_no_difference 'Project.count' do
207 assert_no_difference 'Project.count' do
225 put '/projects/2.xml', {:project => {:name => 'API update', :tracker_ids => [1, 3]}}, credentials('admin')
208 put '/projects/2.xml', {:project => {:name => 'API update', :tracker_ids => [1, 3]}}, credentials('admin')
226 end
209 end
227 assert_response :ok
210 assert_response :ok
228 assert_equal '', @response.body
211 assert_equal '', @response.body
229 project = Project.find(2)
212 project = Project.find(2)
230 assert_equal [1, 3], project.trackers.map(&:id).sort
213 assert_equal [1, 3], project.trackers.map(&:id).sort
231 end
214 end
232
215
233 test "PUT /projects/:id.xml with invalid parameters should return errors" do
216 test "PUT /projects/:id.xml with invalid parameters should return errors" do
234 assert_no_difference('Project.count') do
217 assert_no_difference('Project.count') do
235 put '/projects/2.xml', {:project => {:name => ''}}, credentials('admin')
218 put '/projects/2.xml', {:project => {:name => ''}}, credentials('admin')
236 end
219 end
237
220
238 assert_response :unprocessable_entity
221 assert_response :unprocessable_entity
239 assert_equal 'application/xml', @response.content_type
222 assert_equal 'application/xml', @response.content_type
240 assert_select 'errors error', :text => "Name can't be blank"
223 assert_select 'errors error', :text => "Name can't be blank"
241 end
224 end
242
225
243 test "DELETE /projects/:id.xml should delete the project" do
226 test "DELETE /projects/:id.xml should delete the project" do
244 assert_difference('Project.count',-1) do
227 assert_difference('Project.count',-1) do
245 delete '/projects/2.xml', {}, credentials('admin')
228 delete '/projects/2.xml', {}, credentials('admin')
246 end
229 end
247 assert_response :ok
230 assert_response :ok
248 assert_equal '', @response.body
231 assert_equal '', @response.body
249 assert_nil Project.find_by_id(2)
232 assert_nil Project.find_by_id(2)
250 end
233 end
251 end
234 end
@@ -1,317 +1,278
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base
21 fixtures :users, :members, :member_roles, :roles, :projects
21 fixtures :users, :members, :member_roles, :roles, :projects
22
22
23 def setup
23 def setup
24 Setting.rest_api_enabled = '1'
24 Setting.rest_api_enabled = '1'
25 end
25 end
26
26
27 should_allow_api_authentication(:get, "/users.xml")
28 should_allow_api_authentication(:get, "/users.json")
29 should_allow_api_authentication(:post,
30 '/users.xml',
31 {:user => {
32 :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
33 :mail => 'foo@example.net', :password => 'secret123'
34 }},
35 {:success_code => :created})
36 should_allow_api_authentication(:post,
37 '/users.json',
38 {:user => {
39 :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
40 :mail => 'foo@example.net'
41 }},
42 {:success_code => :created})
43 should_allow_api_authentication(:put,
44 '/users/2.xml',
45 {:user => {
46 :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed',
47 :mail => 'jsmith@somenet.foo'
48 }},
49 {:success_code => :ok})
50 should_allow_api_authentication(:put,
51 '/users/2.json',
52 {:user => {
53 :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed',
54 :mail => 'jsmith@somenet.foo'
55 }},
56 {:success_code => :ok})
57 should_allow_api_authentication(:delete,
58 '/users/2.xml',
59 {},
60 {:success_code => :ok})
61 should_allow_api_authentication(:delete,
62 '/users/2.xml',
63 {},
64 {:success_code => :ok})
65
66 test "GET /users/:id.xml should return the user" do
27 test "GET /users/:id.xml should return the user" do
67 get '/users/2.xml'
28 get '/users/2.xml'
68
29
69 assert_response :success
30 assert_response :success
70 assert_select 'user id', :text => '2'
31 assert_select 'user id', :text => '2'
71 end
32 end
72
33
73 test "GET /users/:id.json should return the user" do
34 test "GET /users/:id.json should return the user" do
74 get '/users/2.json'
35 get '/users/2.json'
75
36
76 assert_response :success
37 assert_response :success
77 json = ActiveSupport::JSON.decode(response.body)
38 json = ActiveSupport::JSON.decode(response.body)
78 assert_kind_of Hash, json
39 assert_kind_of Hash, json
79 assert_kind_of Hash, json['user']
40 assert_kind_of Hash, json['user']
80 assert_equal 2, json['user']['id']
41 assert_equal 2, json['user']['id']
81 end
42 end
82
43
83 test "GET /users/:id.xml with include=memberships should include memberships" do
44 test "GET /users/:id.xml with include=memberships should include memberships" do
84 get '/users/2.xml?include=memberships'
45 get '/users/2.xml?include=memberships'
85
46
86 assert_response :success
47 assert_response :success
87 assert_select 'user memberships', 1
48 assert_select 'user memberships', 1
88 end
49 end
89
50
90 test "GET /users/:id.json with include=memberships should include memberships" do
51 test "GET /users/:id.json with include=memberships should include memberships" do
91 get '/users/2.json?include=memberships'
52 get '/users/2.json?include=memberships'
92
53
93 assert_response :success
54 assert_response :success
94 json = ActiveSupport::JSON.decode(response.body)
55 json = ActiveSupport::JSON.decode(response.body)
95 assert_kind_of Array, json['user']['memberships']
56 assert_kind_of Array, json['user']['memberships']
96 assert_equal [{
57 assert_equal [{
97 "id"=>1,
58 "id"=>1,
98 "project"=>{"name"=>"eCookbook", "id"=>1},
59 "project"=>{"name"=>"eCookbook", "id"=>1},
99 "roles"=>[{"name"=>"Manager", "id"=>1}]
60 "roles"=>[{"name"=>"Manager", "id"=>1}]
100 }], json['user']['memberships']
61 }], json['user']['memberships']
101 end
62 end
102
63
103 test "GET /users/current.xml should require authentication" do
64 test "GET /users/current.xml should require authentication" do
104 get '/users/current.xml'
65 get '/users/current.xml'
105
66
106 assert_response 401
67 assert_response 401
107 end
68 end
108
69
109 test "GET /users/current.xml should return current user" do
70 test "GET /users/current.xml should return current user" do
110 get '/users/current.xml', {}, credentials('jsmith')
71 get '/users/current.xml', {}, credentials('jsmith')
111
72
112 assert_select 'user id', :text => '2'
73 assert_select 'user id', :text => '2'
113 end
74 end
114
75
115 test "GET /users/:id should not return login for other user" do
76 test "GET /users/:id should not return login for other user" do
116 get '/users/3.xml', {}, credentials('jsmith')
77 get '/users/3.xml', {}, credentials('jsmith')
117 assert_response :success
78 assert_response :success
118 assert_select 'user login', 0
79 assert_select 'user login', 0
119 end
80 end
120
81
121 test "GET /users/:id should return login for current user" do
82 test "GET /users/:id should return login for current user" do
122 get '/users/2.xml', {}, credentials('jsmith')
83 get '/users/2.xml', {}, credentials('jsmith')
123 assert_response :success
84 assert_response :success
124 assert_select 'user login', :text => 'jsmith'
85 assert_select 'user login', :text => 'jsmith'
125 end
86 end
126
87
127 test "GET /users/:id should not return api_key for other user" do
88 test "GET /users/:id should not return api_key for other user" do
128 get '/users/3.xml', {}, credentials('jsmith')
89 get '/users/3.xml', {}, credentials('jsmith')
129 assert_response :success
90 assert_response :success
130 assert_select 'user api_key', 0
91 assert_select 'user api_key', 0
131 end
92 end
132
93
133 test "GET /users/:id should return api_key for current user" do
94 test "GET /users/:id should return api_key for current user" do
134 get '/users/2.xml', {}, credentials('jsmith')
95 get '/users/2.xml', {}, credentials('jsmith')
135 assert_response :success
96 assert_response :success
136 assert_select 'user api_key', :text => User.find(2).api_key
97 assert_select 'user api_key', :text => User.find(2).api_key
137 end
98 end
138
99
139 test "GET /users/:id should not return status for standard user" do
100 test "GET /users/:id should not return status for standard user" do
140 get '/users/3.xml', {}, credentials('jsmith')
101 get '/users/3.xml', {}, credentials('jsmith')
141 assert_response :success
102 assert_response :success
142 assert_select 'user status', 0
103 assert_select 'user status', 0
143 end
104 end
144
105
145 test "GET /users/:id should return status for administrators" do
106 test "GET /users/:id should return status for administrators" do
146 get '/users/2.xml', {}, credentials('admin')
107 get '/users/2.xml', {}, credentials('admin')
147 assert_response :success
108 assert_response :success
148 assert_select 'user status', :text => User.find(1).status.to_s
109 assert_select 'user status', :text => User.find(1).status.to_s
149 end
110 end
150
111
151 test "POST /users.xml with valid parameters should create the user" do
112 test "POST /users.xml with valid parameters should create the user" do
152 assert_difference('User.count') do
113 assert_difference('User.count') do
153 post '/users.xml', {
114 post '/users.xml', {
154 :user => {
115 :user => {
155 :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
116 :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
156 :mail => 'foo@example.net', :password => 'secret123',
117 :mail => 'foo@example.net', :password => 'secret123',
157 :mail_notification => 'only_assigned'}
118 :mail_notification => 'only_assigned'}
158 },
119 },
159 credentials('admin')
120 credentials('admin')
160 end
121 end
161
122
162 user = User.order('id DESC').first
123 user = User.order('id DESC').first
163 assert_equal 'foo', user.login
124 assert_equal 'foo', user.login
164 assert_equal 'Firstname', user.firstname
125 assert_equal 'Firstname', user.firstname
165 assert_equal 'Lastname', user.lastname
126 assert_equal 'Lastname', user.lastname
166 assert_equal 'foo@example.net', user.mail
127 assert_equal 'foo@example.net', user.mail
167 assert_equal 'only_assigned', user.mail_notification
128 assert_equal 'only_assigned', user.mail_notification
168 assert !user.admin?
129 assert !user.admin?
169 assert user.check_password?('secret123')
130 assert user.check_password?('secret123')
170
131
171 assert_response :created
132 assert_response :created
172 assert_equal 'application/xml', @response.content_type
133 assert_equal 'application/xml', @response.content_type
173 assert_select 'user id', :text => user.id.to_s
134 assert_select 'user id', :text => user.id.to_s
174 end
135 end
175
136
176 test "POST /users.json with valid parameters should create the user" do
137 test "POST /users.json with valid parameters should create the user" do
177 assert_difference('User.count') do
138 assert_difference('User.count') do
178 post '/users.json', {
139 post '/users.json', {
179 :user => {
140 :user => {
180 :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
141 :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname',
181 :mail => 'foo@example.net', :password => 'secret123',
142 :mail => 'foo@example.net', :password => 'secret123',
182 :mail_notification => 'only_assigned'}
143 :mail_notification => 'only_assigned'}
183 },
144 },
184 credentials('admin')
145 credentials('admin')
185 end
146 end
186
147
187 user = User.order('id DESC').first
148 user = User.order('id DESC').first
188 assert_equal 'foo', user.login
149 assert_equal 'foo', user.login
189 assert_equal 'Firstname', user.firstname
150 assert_equal 'Firstname', user.firstname
190 assert_equal 'Lastname', user.lastname
151 assert_equal 'Lastname', user.lastname
191 assert_equal 'foo@example.net', user.mail
152 assert_equal 'foo@example.net', user.mail
192 assert !user.admin?
153 assert !user.admin?
193
154
194 assert_response :created
155 assert_response :created
195 assert_equal 'application/json', @response.content_type
156 assert_equal 'application/json', @response.content_type
196 json = ActiveSupport::JSON.decode(response.body)
157 json = ActiveSupport::JSON.decode(response.body)
197 assert_kind_of Hash, json
158 assert_kind_of Hash, json
198 assert_kind_of Hash, json['user']
159 assert_kind_of Hash, json['user']
199 assert_equal user.id, json['user']['id']
160 assert_equal user.id, json['user']['id']
200 end
161 end
201
162
202 test "POST /users.xml with with invalid parameters should return errors" do
163 test "POST /users.xml with with invalid parameters should return errors" do
203 assert_no_difference('User.count') do
164 assert_no_difference('User.count') do
204 post '/users.xml', {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}, credentials('admin')
165 post '/users.xml', {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}, credentials('admin')
205 end
166 end
206
167
207 assert_response :unprocessable_entity
168 assert_response :unprocessable_entity
208 assert_equal 'application/xml', @response.content_type
169 assert_equal 'application/xml', @response.content_type
209 assert_select 'errors error', :text => "First name can't be blank"
170 assert_select 'errors error', :text => "First name can't be blank"
210 end
171 end
211
172
212 test "POST /users.json with with invalid parameters should return errors" do
173 test "POST /users.json with with invalid parameters should return errors" do
213 assert_no_difference('User.count') do
174 assert_no_difference('User.count') do
214 post '/users.json', {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}, credentials('admin')
175 post '/users.json', {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}}, credentials('admin')
215 end
176 end
216
177
217 assert_response :unprocessable_entity
178 assert_response :unprocessable_entity
218 assert_equal 'application/json', @response.content_type
179 assert_equal 'application/json', @response.content_type
219 json = ActiveSupport::JSON.decode(response.body)
180 json = ActiveSupport::JSON.decode(response.body)
220 assert_kind_of Hash, json
181 assert_kind_of Hash, json
221 assert json.has_key?('errors')
182 assert json.has_key?('errors')
222 assert_kind_of Array, json['errors']
183 assert_kind_of Array, json['errors']
223 end
184 end
224
185
225 test "PUT /users/:id.xml with valid parameters should update the user" do
186 test "PUT /users/:id.xml with valid parameters should update the user" do
226 assert_no_difference('User.count') do
187 assert_no_difference('User.count') do
227 put '/users/2.xml', {
188 put '/users/2.xml', {
228 :user => {
189 :user => {
229 :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed',
190 :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed',
230 :mail => 'jsmith@somenet.foo'}
191 :mail => 'jsmith@somenet.foo'}
231 },
192 },
232 credentials('admin')
193 credentials('admin')
233 end
194 end
234
195
235 user = User.find(2)
196 user = User.find(2)
236 assert_equal 'jsmith', user.login
197 assert_equal 'jsmith', user.login
237 assert_equal 'John', user.firstname
198 assert_equal 'John', user.firstname
238 assert_equal 'Renamed', user.lastname
199 assert_equal 'Renamed', user.lastname
239 assert_equal 'jsmith@somenet.foo', user.mail
200 assert_equal 'jsmith@somenet.foo', user.mail
240 assert !user.admin?
201 assert !user.admin?
241
202
242 assert_response :ok
203 assert_response :ok
243 assert_equal '', @response.body
204 assert_equal '', @response.body
244 end
205 end
245
206
246 test "PUT /users/:id.json with valid parameters should update the user" do
207 test "PUT /users/:id.json with valid parameters should update the user" do
247 assert_no_difference('User.count') do
208 assert_no_difference('User.count') do
248 put '/users/2.json', {
209 put '/users/2.json', {
249 :user => {
210 :user => {
250 :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed',
211 :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed',
251 :mail => 'jsmith@somenet.foo'}
212 :mail => 'jsmith@somenet.foo'}
252 },
213 },
253 credentials('admin')
214 credentials('admin')
254 end
215 end
255
216
256 user = User.find(2)
217 user = User.find(2)
257 assert_equal 'jsmith', user.login
218 assert_equal 'jsmith', user.login
258 assert_equal 'John', user.firstname
219 assert_equal 'John', user.firstname
259 assert_equal 'Renamed', user.lastname
220 assert_equal 'Renamed', user.lastname
260 assert_equal 'jsmith@somenet.foo', user.mail
221 assert_equal 'jsmith@somenet.foo', user.mail
261 assert !user.admin?
222 assert !user.admin?
262
223
263 assert_response :ok
224 assert_response :ok
264 assert_equal '', @response.body
225 assert_equal '', @response.body
265 end
226 end
266
227
267 test "PUT /users/:id.xml with invalid parameters" do
228 test "PUT /users/:id.xml with invalid parameters" do
268 assert_no_difference('User.count') do
229 assert_no_difference('User.count') do
269 put '/users/2.xml', {
230 put '/users/2.xml', {
270 :user => {
231 :user => {
271 :login => 'jsmith', :firstname => '', :lastname => 'Lastname',
232 :login => 'jsmith', :firstname => '', :lastname => 'Lastname',
272 :mail => 'foo'}
233 :mail => 'foo'}
273 },
234 },
274 credentials('admin')
235 credentials('admin')
275 end
236 end
276
237
277 assert_response :unprocessable_entity
238 assert_response :unprocessable_entity
278 assert_equal 'application/xml', @response.content_type
239 assert_equal 'application/xml', @response.content_type
279 assert_select 'errors error', :text => "First name can't be blank"
240 assert_select 'errors error', :text => "First name can't be blank"
280 end
241 end
281
242
282 test "PUT /users/:id.json with invalid parameters" do
243 test "PUT /users/:id.json with invalid parameters" do
283 assert_no_difference('User.count') do
244 assert_no_difference('User.count') do
284 put '/users/2.json', {
245 put '/users/2.json', {
285 :user => {
246 :user => {
286 :login => 'jsmith', :firstname => '', :lastname => 'Lastname',
247 :login => 'jsmith', :firstname => '', :lastname => 'Lastname',
287 :mail => 'foo'}
248 :mail => 'foo'}
288 },
249 },
289 credentials('admin')
250 credentials('admin')
290 end
251 end
291
252
292 assert_response :unprocessable_entity
253 assert_response :unprocessable_entity
293 assert_equal 'application/json', @response.content_type
254 assert_equal 'application/json', @response.content_type
294 json = ActiveSupport::JSON.decode(response.body)
255 json = ActiveSupport::JSON.decode(response.body)
295 assert_kind_of Hash, json
256 assert_kind_of Hash, json
296 assert json.has_key?('errors')
257 assert json.has_key?('errors')
297 assert_kind_of Array, json['errors']
258 assert_kind_of Array, json['errors']
298 end
259 end
299
260
300 test "DELETE /users/:id.xml should delete the user" do
261 test "DELETE /users/:id.xml should delete the user" do
301 assert_difference('User.count', -1) do
262 assert_difference('User.count', -1) do
302 delete '/users/2.xml', {}, credentials('admin')
263 delete '/users/2.xml', {}, credentials('admin')
303 end
264 end
304
265
305 assert_response :ok
266 assert_response :ok
306 assert_equal '', @response.body
267 assert_equal '', @response.body
307 end
268 end
308
269
309 test "DELETE /users/:id.json should delete the user" do
270 test "DELETE /users/:id.json should delete the user" do
310 assert_difference('User.count', -1) do
271 assert_difference('User.count', -1) do
311 delete '/users/2.json', {}, credentials('admin')
272 delete '/users/2.json', {}, credentials('admin')
312 end
273 end
313
274
314 assert_response :ok
275 assert_response :ok
315 assert_equal '', @response.body
276 assert_equal '', @response.body
316 end
277 end
317 end
278 end
@@ -1,542 +1,293
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 #require 'shoulda'
18 #require 'shoulda'
19 ENV["RAILS_ENV"] = "test"
19 ENV["RAILS_ENV"] = "test"
20 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
21 require 'rails/test_help'
21 require 'rails/test_help'
22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
23
23
24 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
24 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
25 include ObjectHelpers
25 include ObjectHelpers
26
26
27 require 'awesome_nested_set/version'
27 require 'awesome_nested_set/version'
28 require 'net/ldap'
28 require 'net/ldap'
29
29
30 class ActionView::TestCase
30 class ActionView::TestCase
31 helper :application
31 helper :application
32 include ApplicationHelper
32 include ApplicationHelper
33 end
33 end
34
34
35 class ActiveSupport::TestCase
35 class ActiveSupport::TestCase
36 include ActionDispatch::TestProcess
36 include ActionDispatch::TestProcess
37 include Shoulda::Context::Assertions
37 include Shoulda::Context::Assertions
38 include Shoulda::Context::InstanceMethods
38 include Shoulda::Context::InstanceMethods
39 extend Shoulda::Context::ClassMethods
39 extend Shoulda::Context::ClassMethods
40
40
41 self.use_transactional_fixtures = true
41 self.use_transactional_fixtures = true
42 self.use_instantiated_fixtures = false
42 self.use_instantiated_fixtures = false
43
43
44 #ESCAPED_CANT = 'can&#x27;t'
44 #ESCAPED_CANT = 'can&#x27;t'
45 #ESCAPED_UCANT = 'Can&#x27;t'
45 #ESCAPED_UCANT = 'Can&#x27;t'
46 # Rails 4.0.2
46 # Rails 4.0.2
47 ESCAPED_CANT = 'can&#39;t'
47 ESCAPED_CANT = 'can&#39;t'
48 ESCAPED_UCANT = 'Can&#39;t'
48 ESCAPED_UCANT = 'Can&#39;t'
49
49
50 def log_user(login, password)
50 def log_user(login, password)
51 User.anonymous
51 User.anonymous
52 get "/login"
52 get "/login"
53 assert_equal nil, session[:user_id]
53 assert_equal nil, session[:user_id]
54 assert_response :success
54 assert_response :success
55 assert_template "account/login"
55 assert_template "account/login"
56 post "/login", :username => login, :password => password
56 post "/login", :username => login, :password => password
57 assert_equal login, User.find(session[:user_id]).login
57 assert_equal login, User.find(session[:user_id]).login
58 end
58 end
59
59
60 def uploaded_test_file(name, mime)
60 def uploaded_test_file(name, mime)
61 fixture_file_upload("files/#{name}", mime, true)
61 fixture_file_upload("files/#{name}", mime, true)
62 end
62 end
63
63
64 def credentials(user, password=nil)
64 def credentials(user, password=nil)
65 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
65 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
66 end
66 end
67
67
68 # Mock out a file
68 # Mock out a file
69 def self.mock_file
69 def self.mock_file
70 file = 'a_file.png'
70 file = 'a_file.png'
71 file.stubs(:size).returns(32)
71 file.stubs(:size).returns(32)
72 file.stubs(:original_filename).returns('a_file.png')
72 file.stubs(:original_filename).returns('a_file.png')
73 file.stubs(:content_type).returns('image/png')
73 file.stubs(:content_type).returns('image/png')
74 file.stubs(:read).returns(false)
74 file.stubs(:read).returns(false)
75 file
75 file
76 end
76 end
77
77
78 def mock_file
78 def mock_file
79 self.class.mock_file
79 self.class.mock_file
80 end
80 end
81
81
82 def mock_file_with_options(options={})
82 def mock_file_with_options(options={})
83 file = ''
83 file = ''
84 file.stubs(:size).returns(32)
84 file.stubs(:size).returns(32)
85 original_filename = options[:original_filename] || nil
85 original_filename = options[:original_filename] || nil
86 file.stubs(:original_filename).returns(original_filename)
86 file.stubs(:original_filename).returns(original_filename)
87 content_type = options[:content_type] || nil
87 content_type = options[:content_type] || nil
88 file.stubs(:content_type).returns(content_type)
88 file.stubs(:content_type).returns(content_type)
89 file.stubs(:read).returns(false)
89 file.stubs(:read).returns(false)
90 file
90 file
91 end
91 end
92
92
93 # Use a temporary directory for attachment related tests
93 # Use a temporary directory for attachment related tests
94 def set_tmp_attachments_directory
94 def set_tmp_attachments_directory
95 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
95 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
96 unless File.directory?("#{Rails.root}/tmp/test/attachments")
96 unless File.directory?("#{Rails.root}/tmp/test/attachments")
97 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
97 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
98 end
98 end
99 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
99 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
100 end
100 end
101
101
102 def set_fixtures_attachments_directory
102 def set_fixtures_attachments_directory
103 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
103 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
104 end
104 end
105
105
106 def with_settings(options, &block)
106 def with_settings(options, &block)
107 saved_settings = options.keys.inject({}) do |h, k|
107 saved_settings = options.keys.inject({}) do |h, k|
108 h[k] = case Setting[k]
108 h[k] = case Setting[k]
109 when Symbol, false, true, nil
109 when Symbol, false, true, nil
110 Setting[k]
110 Setting[k]
111 else
111 else
112 Setting[k].dup
112 Setting[k].dup
113 end
113 end
114 h
114 h
115 end
115 end
116 options.each {|k, v| Setting[k] = v}
116 options.each {|k, v| Setting[k] = v}
117 yield
117 yield
118 ensure
118 ensure
119 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
119 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
120 end
120 end
121
121
122 # Yields the block with user as the current user
122 # Yields the block with user as the current user
123 def with_current_user(user, &block)
123 def with_current_user(user, &block)
124 saved_user = User.current
124 saved_user = User.current
125 User.current = user
125 User.current = user
126 yield
126 yield
127 ensure
127 ensure
128 User.current = saved_user
128 User.current = saved_user
129 end
129 end
130
130
131 def with_locale(locale, &block)
131 def with_locale(locale, &block)
132 saved_localed = ::I18n.locale
132 saved_localed = ::I18n.locale
133 ::I18n.locale = locale
133 ::I18n.locale = locale
134 yield
134 yield
135 ensure
135 ensure
136 ::I18n.locale = saved_localed
136 ::I18n.locale = saved_localed
137 end
137 end
138
138
139 def change_user_password(login, new_password)
139 def change_user_password(login, new_password)
140 user = User.where(:login => login).first
140 user = User.where(:login => login).first
141 user.password, user.password_confirmation = new_password, new_password
141 user.password, user.password_confirmation = new_password, new_password
142 user.save!
142 user.save!
143 end
143 end
144
144
145 def self.ldap_configured?
145 def self.ldap_configured?
146 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
146 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
147 return @test_ldap.bind
147 return @test_ldap.bind
148 rescue Exception => e
148 rescue Exception => e
149 # LDAP is not listening
149 # LDAP is not listening
150 return nil
150 return nil
151 end
151 end
152
152
153 def self.convert_installed?
153 def self.convert_installed?
154 Redmine::Thumbnail.convert_available?
154 Redmine::Thumbnail.convert_available?
155 end
155 end
156
156
157 # Returns the path to the test +vendor+ repository
157 # Returns the path to the test +vendor+ repository
158 def self.repository_path(vendor)
158 def self.repository_path(vendor)
159 path = Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
159 path = Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
160 # Unlike ruby, JRuby returns Rails.root with backslashes under Windows
160 # Unlike ruby, JRuby returns Rails.root with backslashes under Windows
161 path.tr("\\", "/")
161 path.tr("\\", "/")
162 end
162 end
163
163
164 # Returns the url of the subversion test repository
164 # Returns the url of the subversion test repository
165 def self.subversion_repository_url
165 def self.subversion_repository_url
166 path = repository_path('subversion')
166 path = repository_path('subversion')
167 path = '/' + path unless path.starts_with?('/')
167 path = '/' + path unless path.starts_with?('/')
168 "file://#{path}"
168 "file://#{path}"
169 end
169 end
170
170
171 # Returns true if the +vendor+ test repository is configured
171 # Returns true if the +vendor+ test repository is configured
172 def self.repository_configured?(vendor)
172 def self.repository_configured?(vendor)
173 File.directory?(repository_path(vendor))
173 File.directory?(repository_path(vendor))
174 end
174 end
175
175
176 def repository_path_hash(arr)
176 def repository_path_hash(arr)
177 hs = {}
177 hs = {}
178 hs[:path] = arr.join("/")
178 hs[:path] = arr.join("/")
179 hs[:param] = arr.join("/")
179 hs[:param] = arr.join("/")
180 hs
180 hs
181 end
181 end
182
182
183 def assert_save(object)
183 def assert_save(object)
184 saved = object.save
184 saved = object.save
185 message = "#{object.class} could not be saved"
185 message = "#{object.class} could not be saved"
186 errors = object.errors.full_messages.map {|m| "- #{m}"}
186 errors = object.errors.full_messages.map {|m| "- #{m}"}
187 message << ":\n#{errors.join("\n")}" if errors.any?
187 message << ":\n#{errors.join("\n")}" if errors.any?
188 assert_equal true, saved, message
188 assert_equal true, saved, message
189 end
189 end
190
190
191 def assert_select_error(arg)
191 def assert_select_error(arg)
192 assert_select '#errorExplanation', :text => arg
192 assert_select '#errorExplanation', :text => arg
193 end
193 end
194
194
195 def assert_include(expected, s, message=nil)
195 def assert_include(expected, s, message=nil)
196 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
196 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
197 end
197 end
198
198
199 def assert_not_include(expected, s, message=nil)
199 def assert_not_include(expected, s, message=nil)
200 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
200 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
201 end
201 end
202
202
203 def assert_select_in(text, *args, &block)
203 def assert_select_in(text, *args, &block)
204 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
204 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
205 assert_select(d, *args, &block)
205 assert_select(d, *args, &block)
206 end
206 end
207
207
208 def assert_mail_body_match(expected, mail, message=nil)
208 def assert_mail_body_match(expected, mail, message=nil)
209 if expected.is_a?(String)
209 if expected.is_a?(String)
210 assert_include expected, mail_body(mail), message
210 assert_include expected, mail_body(mail), message
211 else
211 else
212 assert_match expected, mail_body(mail), message
212 assert_match expected, mail_body(mail), message
213 end
213 end
214 end
214 end
215
215
216 def assert_mail_body_no_match(expected, mail, message=nil)
216 def assert_mail_body_no_match(expected, mail, message=nil)
217 if expected.is_a?(String)
217 if expected.is_a?(String)
218 assert_not_include expected, mail_body(mail), message
218 assert_not_include expected, mail_body(mail), message
219 else
219 else
220 assert_no_match expected, mail_body(mail), message
220 assert_no_match expected, mail_body(mail), message
221 end
221 end
222 end
222 end
223
223
224 def mail_body(mail)
224 def mail_body(mail)
225 mail.parts.first.body.encoded
225 mail.parts.first.body.encoded
226 end
226 end
227
227
228 # awesome_nested_set new node lft and rgt value changed this refactor revision.
228 # awesome_nested_set new node lft and rgt value changed this refactor revision.
229 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb938e40200cd90714dc69247ef017c61
229 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb938e40200cd90714dc69247ef017c61
230 # The reason of behavior change is that "self.class.base_class.unscoped" was added to this line.
230 # The reason of behavior change is that "self.class.base_class.unscoped" was added to this line.
231 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb9#diff-f61b59a5e6319024e211b0ffdd0e4ef1R273
231 # https://github.com/collectiveidea/awesome_nested_set/commit/199fca9bb9#diff-f61b59a5e6319024e211b0ffdd0e4ef1R273
232 # It seems correct behavior because of this line comment.
232 # It seems correct behavior because of this line comment.
233 # https://github.com/collectiveidea/awesome_nested_set/blame/199fca9bb9/lib/awesome_nested_set/model.rb#L278
233 # https://github.com/collectiveidea/awesome_nested_set/blame/199fca9bb9/lib/awesome_nested_set/model.rb#L278
234 def new_issue_lft
234 def new_issue_lft
235 # ::AwesomeNestedSet::VERSION > "2.1.6" ? Issue.maximum(:rgt) + 1 : 1
235 # ::AwesomeNestedSet::VERSION > "2.1.6" ? Issue.maximum(:rgt) + 1 : 1
236 Issue.maximum(:rgt) + 1
236 Issue.maximum(:rgt) + 1
237 end
237 end
238 end
238 end
239
239
240 module Redmine
240 module Redmine
241 class RoutingTest < ActionDispatch::IntegrationTest
241 class RoutingTest < ActionDispatch::IntegrationTest
242 def should_route(arg)
242 def should_route(arg)
243 arg = arg.dup
243 arg = arg.dup
244 request = arg.keys.detect {|key| key.is_a?(String)}
244 request = arg.keys.detect {|key| key.is_a?(String)}
245 raise ArgumentError unless request
245 raise ArgumentError unless request
246 options = arg.slice!(request)
246 options = arg.slice!(request)
247
247
248 raise ArgumentError unless request =~ /\A(GET|POST|PUT|PATCH|DELETE)\s+(.+)\z/
248 raise ArgumentError unless request =~ /\A(GET|POST|PUT|PATCH|DELETE)\s+(.+)\z/
249 method, path = $1.downcase.to_sym, $2
249 method, path = $1.downcase.to_sym, $2
250
250
251 raise ArgumentError unless arg.values.first =~ /\A(.+)#(.+)\z/
251 raise ArgumentError unless arg.values.first =~ /\A(.+)#(.+)\z/
252 controller, action = $1, $2
252 controller, action = $1, $2
253
253
254 assert_routing(
254 assert_routing(
255 {:method => method, :path => path},
255 {:method => method, :path => path},
256 options.merge(:controller => controller, :action => action)
256 options.merge(:controller => controller, :action => action)
257 )
257 )
258 end
258 end
259 end
259 end
260
260
261 module ApiTest
261 module ApiTest
262 API_FORMATS = %w(json xml).freeze
262 API_FORMATS = %w(json xml).freeze
263
263
264 # Base class for API tests
264 # Base class for API tests
265 class Base < ActionDispatch::IntegrationTest
265 class Base < ActionDispatch::IntegrationTest
266 # Test that a request allows the three types of API authentication
267 #
268 # * HTTP Basic with username and password
269 # * HTTP Basic with an api key for the username
270 # * Key based with the key=X parameter
271 #
272 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
273 # @param [String] url the request url
274 # @param [optional, Hash] parameters additional request parameters
275 # @param [optional, Hash] options additional options
276 # @option options [Symbol] :success_code Successful response code (:success)
277 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
278 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
279 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
280 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
281 should_allow_key_based_auth(http_method, url, parameters, options)
282 end
283
284 # Test that a request allows the username and password for HTTP BASIC
285 #
286 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
287 # @param [String] url the request url
288 # @param [optional, Hash] parameters additional request parameters
289 # @param [optional, Hash] options additional options
290 # @option options [Symbol] :success_code Successful response code (:success)
291 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
292 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
293 success_code = options[:success_code] || :success
294 failure_code = options[:failure_code] || :unauthorized
295
296 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
297 context "with a valid HTTP authentication" do
298 setup do
299 @user = User.generate! do |user|
300 user.admin = true
301 user.password = 'my_password'
302 end
303 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
304 end
305
306 should_respond_with success_code
307 should_respond_with_content_type_based_on_url(url)
308 should "login as the user" do
309 assert_equal @user, User.current
310 end
311 end
312
313 context "with an invalid HTTP authentication" do
314 setup do
315 @user = User.generate!
316 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
317 end
318
319 should_respond_with failure_code
320 should_respond_with_content_type_based_on_url(url)
321 should "not login as the user" do
322 assert_equal User.anonymous, User.current
323 end
324 end
325
326 context "without credentials" do
327 setup do
328 send(http_method, url, parameters)
329 end
330
331 should_respond_with failure_code
332 should_respond_with_content_type_based_on_url(url)
333 should "include_www_authenticate_header" do
334 assert @controller.response.headers.has_key?('WWW-Authenticate')
335 end
336 end
337 end
338 end
339
340 # Test that a request allows the API key with HTTP BASIC
341 #
342 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
343 # @param [String] url the request url
344 # @param [optional, Hash] parameters additional request parameters
345 # @param [optional, Hash] options additional options
346 # @option options [Symbol] :success_code Successful response code (:success)
347 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
348 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
349 success_code = options[:success_code] || :success
350 failure_code = options[:failure_code] || :unauthorized
351
352 context "should allow http basic auth with a key for #{http_method} #{url}" do
353 context "with a valid HTTP authentication using the API token" do
354 setup do
355 @user = User.generate! do |user|
356 user.admin = true
357 end
358 @token = Token.create!(:user => @user, :action => 'api')
359 send(http_method, url, parameters, credentials(@token.value, 'X'))
360 end
361 should_respond_with success_code
362 should_respond_with_content_type_based_on_url(url)
363 should_be_a_valid_response_string_based_on_url(url)
364 should "login as the user" do
365 assert_equal @user, User.current
366 end
367 end
368
369 context "with an invalid HTTP authentication" do
370 setup do
371 @user = User.generate!
372 @token = Token.create!(:user => @user, :action => 'feeds')
373 send(http_method, url, parameters, credentials(@token.value, 'X'))
374 end
375 should_respond_with failure_code
376 should_respond_with_content_type_based_on_url(url)
377 should "not login as the user" do
378 assert_equal User.anonymous, User.current
379 end
380 end
381 end
382 end
383
384 # Test that a request allows full key authentication
385 #
386 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
387 # @param [String] url the request url, without the key=ZXY parameter
388 # @param [optional, Hash] parameters additional request parameters
389 # @param [optional, Hash] options additional options
390 # @option options [Symbol] :success_code Successful response code (:success)
391 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
392 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
393 success_code = options[:success_code] || :success
394 failure_code = options[:failure_code] || :unauthorized
395
396 context "should allow key based auth using key=X for #{http_method} #{url}" do
397 context "with a valid api token" do
398 setup do
399 @user = User.generate! do |user|
400 user.admin = true
401 end
402 @token = Token.create!(:user => @user, :action => 'api')
403 # Simple url parse to add on ?key= or &key=
404 request_url = if url.match(/\?/)
405 url + "&key=#{@token.value}"
406 else
407 url + "?key=#{@token.value}"
408 end
409 send(http_method, request_url, parameters)
410 end
411 should_respond_with success_code
412 should_respond_with_content_type_based_on_url(url)
413 should_be_a_valid_response_string_based_on_url(url)
414 should "login as the user" do
415 assert_equal @user, User.current
416 end
417 end
418
419 context "with an invalid api token" do
420 setup do
421 @user = User.generate! do |user|
422 user.admin = true
423 end
424 @token = Token.create!(:user => @user, :action => 'feeds')
425 # Simple url parse to add on ?key= or &key=
426 request_url = if url.match(/\?/)
427 url + "&key=#{@token.value}"
428 else
429 url + "?key=#{@token.value}"
430 end
431 send(http_method, request_url, parameters)
432 end
433 should_respond_with failure_code
434 should_respond_with_content_type_based_on_url(url)
435 should "not login as the user" do
436 assert_equal User.anonymous, User.current
437 end
438 end
439 end
440
441 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
442 setup do
443 @user = User.generate! do |user|
444 user.admin = true
445 end
446 @token = Token.create!(:user => @user, :action => 'api')
447 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
448 end
449 should_respond_with success_code
450 should_respond_with_content_type_based_on_url(url)
451 should_be_a_valid_response_string_based_on_url(url)
452 should "login as the user" do
453 assert_equal @user, User.current
454 end
455 end
456 end
457
458 # Uses should_respond_with_content_type based on what's in the url:
459 #
460 # '/project/issues.xml' => should_respond_with_content_type :xml
461 # '/project/issues.json' => should_respond_with_content_type :json
462 #
463 # @param [String] url Request
464 def self.should_respond_with_content_type_based_on_url(url)
465 case
466 when url.match(/xml/i)
467 should "respond with XML" do
468 assert_equal 'application/xml', @response.content_type
469 end
470 when url.match(/json/i)
471 should "respond with JSON" do
472 assert_equal 'application/json', @response.content_type
473 end
474 else
475 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
476 end
477 end
478
479 # Uses the url to assert which format the response should be in
480 #
481 # '/project/issues.xml' => should_be_a_valid_xml_string
482 # '/project/issues.json' => should_be_a_valid_json_string
483 #
484 # @param [String] url Request
485 def self.should_be_a_valid_response_string_based_on_url(url)
486 case
487 when url.match(/xml/i)
488 should_be_a_valid_xml_string
489 when url.match(/json/i)
490 should_be_a_valid_json_string
491 else
492 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
493 end
494 end
495
496 # Checks that the response is a valid JSON string
497 def self.should_be_a_valid_json_string
498 should "be a valid JSON string (or empty)" do
499 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
500 end
501 end
502
503 # Checks that the response is a valid XML string
504 def self.should_be_a_valid_xml_string
505 should "be a valid XML string" do
506 assert REXML::Document.new(response.body)
507 end
508 end
509
510 def self.should_respond_with(status)
511 should "respond with #{status}" do
512 assert_response status
513 end
514 end
515 end
266 end
516
267
517 class Routing < Redmine::RoutingTest
268 class Routing < Redmine::RoutingTest
518 def should_route(arg)
269 def should_route(arg)
519 arg = arg.dup
270 arg = arg.dup
520 request = arg.keys.detect {|key| key.is_a?(String)}
271 request = arg.keys.detect {|key| key.is_a?(String)}
521 raise ArgumentError unless request
272 raise ArgumentError unless request
522 options = arg.slice!(request)
273 options = arg.slice!(request)
523
274
524 API_FORMATS.each do |format|
275 API_FORMATS.each do |format|
525 format_request = request.sub /$/, ".#{format}"
276 format_request = request.sub /$/, ".#{format}"
526 super options.merge(format_request => arg[request], :format => format)
277 super options.merge(format_request => arg[request], :format => format)
527 end
278 end
528 end
279 end
529 end
280 end
530 end
281 end
531 end
282 end
532
283
533 # URL helpers do not work with config.threadsafe!
284 # URL helpers do not work with config.threadsafe!
534 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
285 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
535 ActionView::TestCase::TestController.instance_eval do
286 ActionView::TestCase::TestController.instance_eval do
536 helper Rails.application.routes.url_helpers
287 helper Rails.application.routes.url_helpers
537 end
288 end
538 ActionView::TestCase::TestController.class_eval do
289 ActionView::TestCase::TestController.class_eval do
539 def _routes
290 def _routes
540 Rails.application.routes
291 Rails.application.routes
541 end
292 end
542 end
293 end
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now