##// END OF EJS Templates
Use assert_nil instead of assert_equal....
Jean-Philippe Lang -
r15678:bf5dade8df89
parent child
Show More
@@ -1,235 +1,235
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class ProjectEnumerationsControllerTest < Redmine::ControllerTest
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :enumerations, :users, :issue_categories,
23 23 :projects_trackers,
24 24 :roles,
25 25 :member_roles,
26 26 :members,
27 27 :enabled_modules,
28 28 :custom_fields, :custom_fields_projects,
29 29 :custom_fields_trackers, :custom_values,
30 30 :time_entries
31 31
32 32 self.use_transactional_fixtures = false
33 33
34 34 def setup
35 35 @request.session[:user_id] = nil
36 36 Setting.default_language = 'en'
37 37 end
38 38
39 39 def test_update_to_override_system_activities
40 40 @request.session[:user_id] = 2 # manager
41 41 billable_field = TimeEntryActivityCustomField.find_by_name("Billable")
42 42
43 43 put :update, :project_id => 1, :enumerations => {
44 44 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate
45 45 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value
46 46 "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value
47 47 "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes
48 48 }
49 49
50 50 assert_response :redirect
51 51 assert_redirected_to '/projects/ecookbook/settings/activities'
52 52
53 53 # Created project specific activities...
54 54 project = Project.find('ecookbook')
55 55
56 56 # ... Design
57 57 design = project.time_entry_activities.find_by_name("Design")
58 58 assert design, "Project activity not found"
59 59
60 60 assert_equal 9, design.parent_id # Relate to the system activity
61 61 assert_not_equal design.parent.id, design.id # Different records
62 62 assert_equal design.parent.name, design.name # Same name
63 63 assert !design.active?
64 64
65 65 # ... Development
66 66 development = project.time_entry_activities.find_by_name("Development")
67 67 assert development, "Project activity not found"
68 68
69 69 assert_equal 10, development.parent_id # Relate to the system activity
70 70 assert_not_equal development.parent.id, development.id # Different records
71 71 assert_equal development.parent.name, development.name # Same name
72 72 assert development.active?
73 73 assert_equal "0", development.custom_value_for(billable_field).value
74 74
75 75 # ... Inactive Activity
76 76 previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity")
77 77 assert previously_inactive, "Project activity not found"
78 78
79 79 assert_equal 14, previously_inactive.parent_id # Relate to the system activity
80 80 assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records
81 81 assert_equal previously_inactive.parent.name, previously_inactive.name # Same name
82 82 assert previously_inactive.active?
83 83 assert_equal "1", previously_inactive.custom_value_for(billable_field).value
84 84
85 85 # ... QA
86 assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified"
86 assert_nil project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified"
87 87 end
88 88
89 89 def test_update_will_update_project_specific_activities
90 90 @request.session[:user_id] = 2 # manager
91 91
92 92 project_activity = TimeEntryActivity.new({
93 93 :name => 'Project Specific',
94 94 :parent => TimeEntryActivity.first,
95 95 :project => Project.find(1),
96 96 :active => true
97 97 })
98 98 assert project_activity.save
99 99 project_activity_two = TimeEntryActivity.new({
100 100 :name => 'Project Specific Two',
101 101 :parent => TimeEntryActivity.last,
102 102 :project => Project.find(1),
103 103 :active => true
104 104 })
105 105 assert project_activity_two.save
106 106
107 107
108 108 put :update, :project_id => 1, :enumerations => {
109 109 project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate
110 110 project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate
111 111 }
112 112
113 113 assert_response :redirect
114 114 assert_redirected_to '/projects/ecookbook/settings/activities'
115 115
116 116 # Created project specific activities...
117 117 project = Project.find('ecookbook')
118 118 assert_equal 2, project.time_entry_activities.count
119 119
120 120 activity_one = project.time_entry_activities.find_by_name(project_activity.name)
121 121 assert activity_one, "Project activity not found"
122 122 assert_equal project_activity.id, activity_one.id
123 123 assert !activity_one.active?
124 124
125 125 activity_two = project.time_entry_activities.find_by_name(project_activity_two.name)
126 126 assert activity_two, "Project activity not found"
127 127 assert_equal project_activity_two.id, activity_two.id
128 128 assert !activity_two.active?
129 129 end
130 130
131 131 def test_update_when_creating_new_activities_will_convert_existing_data
132 132 assert_equal 3, TimeEntry.where(:activity_id => 9, :project_id => 1).count
133 133
134 134 @request.session[:user_id] = 2 # manager
135 135 put :update, :project_id => 1, :enumerations => {
136 136 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate
137 137 }
138 138 assert_response :redirect
139 139
140 140 # No more TimeEntries using the system activity
141 141 assert_equal 0, TimeEntry.where(:activity_id => 9, :project_id => 1).count,
142 142 "Time Entries still assigned to system activities"
143 143 # All TimeEntries using project activity
144 144 project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1)
145 145 assert_equal 3, TimeEntry.where(:activity_id => project_specific_activity.id,
146 146 :project_id => 1).count
147 147 "No Time Entries assigned to the project activity"
148 148 end
149 149
150 150 def test_update_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised
151 151 # TODO: Need to cause an exception on create but these tests
152 152 # aren't setup for mocking. Just create a record now so the
153 153 # second one is a dupicate
154 154 parent = TimeEntryActivity.find(9)
155 155 TimeEntryActivity.create!({:name => parent.name, :project_id => 1,
156 156 :position => parent.position, :active => true, :parent_id => 9})
157 157 TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1),
158 158 :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'})
159 159 assert_equal 3, TimeEntry.where(:activity_id => 9, :project_id => 1).count
160 160 assert_equal 1, TimeEntry.where(:activity_id => 10, :project_id => 1).count
161 161
162 162 @request.session[:user_id] = 2 # manager
163 163 put :update, :project_id => 1, :enumerations => {
164 164 # Design
165 165 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"},
166 166 # Development, Change custom value
167 167 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}
168 168 }
169 169 assert_response :redirect
170 170
171 171 # TimeEntries shouldn't have been reassigned on the failed record
172 172 assert_equal 3, TimeEntry.where(:activity_id => 9,
173 173 :project_id => 1).count
174 174 "Time Entries are not assigned to system activities"
175 175 # TimeEntries shouldn't have been reassigned on the saved record either
176 176 assert_equal 1, TimeEntry.where(:activity_id => 10,
177 177 :project_id => 1).count
178 178 "Time Entries are not assigned to system activities"
179 179 end
180 180
181 181 def test_destroy
182 182 @request.session[:user_id] = 2 # manager
183 183 project_activity = TimeEntryActivity.new({
184 184 :name => 'Project Specific',
185 185 :parent => TimeEntryActivity.first,
186 186 :project => Project.find(1),
187 187 :active => true
188 188 })
189 189 assert project_activity.save
190 190 project_activity_two = TimeEntryActivity.new({
191 191 :name => 'Project Specific Two',
192 192 :parent => TimeEntryActivity.last,
193 193 :project => Project.find(1),
194 194 :active => true
195 195 })
196 196 assert project_activity_two.save
197 197
198 198 delete :destroy, :project_id => 1
199 199 assert_response :redirect
200 200 assert_redirected_to '/projects/ecookbook/settings/activities'
201 201
202 202 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
203 203 assert_nil TimeEntryActivity.find_by_id(project_activity_two.id)
204 204 end
205 205
206 206 def test_destroy_should_reassign_time_entries_back_to_the_system_activity
207 207 @request.session[:user_id] = 2 # manager
208 208 project_activity = TimeEntryActivity.new({
209 209 :name => 'Project Specific Design',
210 210 :parent => TimeEntryActivity.find(9),
211 211 :project => Project.find(1),
212 212 :active => true
213 213 })
214 214 assert project_activity.save
215 215 assert TimeEntry.where(["project_id = ? AND activity_id = ?", 1, 9]).
216 216 update_all("activity_id = '#{project_activity.id}'")
217 217 assert_equal 3, TimeEntry.where(:activity_id => project_activity.id,
218 218 :project_id => 1).count
219 219 delete :destroy, :project_id => 1
220 220 assert_response :redirect
221 221 assert_redirected_to '/projects/ecookbook/settings/activities'
222 222
223 223 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
224 224 assert_equal 0, TimeEntry.where(
225 225 :activity_id => project_activity.id,
226 226 :project_id => 1
227 227 ).count,
228 228 "TimeEntries still assigned to project specific activity"
229 229 assert_equal 3, TimeEntry.where(
230 230 :activity_id => 9,
231 231 :project_id => 1
232 232 ).count,
233 233 "TimeEntries still assigned to project specific activity"
234 234 end
235 235 end
@@ -1,635 +1,635
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class UsersControllerTest < Redmine::ControllerTest
21 21 include Redmine::I18n
22 22
23 23 fixtures :users, :email_addresses, :projects, :members, :member_roles, :roles,
24 24 :custom_fields, :custom_values, :groups_users,
25 25 :auth_sources,
26 26 :enabled_modules,
27 27 :issues, :issue_statuses,
28 28 :trackers
29 29
30 30 def setup
31 31 User.current = nil
32 32 @request.session[:user_id] = 1 # admin
33 33 end
34 34
35 35 def test_index
36 36 get :index
37 37 assert_response :success
38 38 assert_select 'table.users'
39 39 assert_select 'tr.user.active'
40 40 assert_select 'tr.user.locked', 0
41 41 end
42 42
43 43 def test_index_with_status_filter
44 44 get :index, :params => {:status => 3}
45 45 assert_response :success
46 46 assert_select 'tr.user.active', 0
47 47 assert_select 'tr.user.locked'
48 48 end
49 49
50 50 def test_index_with_name_filter
51 51 get :index, :params => {:name => 'john'}
52 52 assert_response :success
53 53 assert_select 'tr.user td.username', :text => 'jsmith'
54 54 assert_select 'tr.user', 1
55 55 end
56 56
57 57 def test_index_with_group_filter
58 58 get :index, :params => {:group_id => '10'}
59 59 assert_response :success
60 60
61 61 assert_select 'tr.user', Group.find(10).users.count
62 62 assert_select 'select[name=group_id]' do
63 63 assert_select 'option[value="10"][selected=selected]'
64 64 end
65 65 end
66 66
67 67 def test_show
68 68 @request.session[:user_id] = nil
69 69 get :show, :params => {:id => 2}
70 70 assert_response :success
71 71 assert_select 'h2', :text => /John Smith/
72 72 end
73 73
74 74 def test_show_should_display_visible_custom_fields
75 75 @request.session[:user_id] = nil
76 76 UserCustomField.find_by_name('Phone number').update_attribute :visible, true
77 77 get :show, :params => {:id => 2}
78 78 assert_response :success
79 79
80 80 assert_select 'li', :text => /Phone number/
81 81 end
82 82
83 83 def test_show_should_not_display_hidden_custom_fields
84 84 @request.session[:user_id] = nil
85 85 UserCustomField.find_by_name('Phone number').update_attribute :visible, false
86 86 get :show, :params => {:id => 2}
87 87 assert_response :success
88 88
89 89 assert_select 'li', :text => /Phone number/, :count => 0
90 90 end
91 91
92 92 def test_show_should_not_fail_when_custom_values_are_nil
93 93 user = User.find(2)
94 94
95 95 # Create a custom field to illustrate the issue
96 96 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
97 97 custom_value = user.custom_values.build(:custom_field => custom_field).save!
98 98
99 99 get :show, :params => {:id => 2}
100 100 assert_response :success
101 101 end
102 102
103 103 def test_show_inactive
104 104 @request.session[:user_id] = nil
105 105 get :show, :params => {:id => 5}
106 106 assert_response 404
107 107 end
108 108
109 109 def test_show_inactive_by_admin
110 110 @request.session[:user_id] = 1
111 111 get :show, :params => {:id => 5}
112 112 assert_response 200
113 113 assert_select 'h2', :text => /Dave2 Lopper2/
114 114 end
115 115
116 116 def test_show_user_who_is_not_visible_should_return_404
117 117 Role.anonymous.update! :users_visibility => 'members_of_visible_projects'
118 118 user = User.generate!
119 119
120 120 @request.session[:user_id] = nil
121 121 get :show, :params => {:id => user.id}
122 122 assert_response 404
123 123 end
124 124
125 125 def test_show_displays_memberships_based_on_project_visibility
126 126 @request.session[:user_id] = 1
127 127 get :show, :params => {:id => 2}
128 128 assert_response :success
129 129
130 130 # membership of private project admin can see
131 131 assert_select 'li a', :text => "OnlineStore"
132 132 end
133 133
134 134 def test_show_current_should_require_authentication
135 135 @request.session[:user_id] = nil
136 136 get :show, :params => {:id => 'current'}
137 137 assert_response 302
138 138 end
139 139
140 140 def test_show_current
141 141 @request.session[:user_id] = 2
142 142 get :show, :params => {:id => 'current'}
143 143 assert_response :success
144 144 assert_select 'h2', :text => /John Smith/
145 145 end
146 146
147 147 def test_new
148 148 get :new
149 149 assert_response :success
150 150 assert_select 'input[name=?]', 'user[login]'
151 151 end
152 152
153 153 def test_create
154 154 Setting.bcc_recipients = '1'
155 155
156 156 assert_difference 'User.count' do
157 157 assert_difference 'ActionMailer::Base.deliveries.size' do
158 158 post :create, :params => {
159 159 :user => {
160 160 :firstname => 'John',
161 161 :lastname => 'Doe',
162 162 :login => 'jdoe',
163 163 :password => 'secret123',
164 164 :password_confirmation => 'secret123',
165 165 :mail => 'jdoe@gmail.com',
166 166 :mail_notification => 'none'
167 167 },
168 168 :send_information => '1'
169 169 }
170 170 end
171 171 end
172 172
173 173 user = User.order('id DESC').first
174 174 assert_redirected_to :controller => 'users', :action => 'edit', :id => user.id
175 175
176 176 assert_equal 'John', user.firstname
177 177 assert_equal 'Doe', user.lastname
178 178 assert_equal 'jdoe', user.login
179 179 assert_equal 'jdoe@gmail.com', user.mail
180 180 assert_equal 'none', user.mail_notification
181 181 assert user.check_password?('secret123')
182 182
183 183 mail = ActionMailer::Base.deliveries.last
184 184 assert_not_nil mail
185 185 assert_equal [user.mail], mail.bcc
186 186 assert_mail_body_match 'secret', mail
187 187 end
188 188
189 189 def test_create_with_preferences
190 190 assert_difference 'User.count' do
191 191 post :create, :params => {
192 192 :user => {
193 193 :firstname => 'John',
194 194 :lastname => 'Doe',
195 195 :login => 'jdoe',
196 196 :password => 'secret123',
197 197 :password_confirmation => 'secret123',
198 198 :mail => 'jdoe@gmail.com',
199 199 :mail_notification => 'none'
200 200 },
201 201 :pref => {
202 202 'hide_mail' => '1',
203 203 'time_zone' => 'Paris',
204 204 'comments_sorting' => 'desc',
205 205 'warn_on_leaving_unsaved' => '0',
206 206 'textarea_font' => 'proportional'
207 207 }
208 208 }
209 209 end
210 210 user = User.order('id DESC').first
211 211 assert_equal 'jdoe', user.login
212 212 assert_equal true, user.pref.hide_mail
213 213 assert_equal 'Paris', user.pref.time_zone
214 214 assert_equal 'desc', user.pref[:comments_sorting]
215 215 assert_equal '0', user.pref[:warn_on_leaving_unsaved]
216 216 assert_equal 'proportional', user.pref[:textarea_font]
217 217 end
218 218
219 219 def test_create_with_generate_password_should_email_the_password
220 220 assert_difference 'User.count' do
221 221 post :create, :params => {
222 222 :user => {
223 223 :login => 'randompass',
224 224 :firstname => 'Random',
225 225 :lastname => 'Pass',
226 226 :mail => 'randompass@example.net',
227 227 :language => 'en',
228 228 :generate_password => '1',
229 229 :password => '',
230 230 :password_confirmation => ''
231 231 },
232 232 :send_information => 1
233 233 }
234 234 end
235 235 user = User.order('id DESC').first
236 236 assert_equal 'randompass', user.login
237 237
238 238 mail = ActionMailer::Base.deliveries.last
239 239 assert_not_nil mail
240 240 m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/)
241 241 assert m
242 242 password = m[1]
243 243 assert user.check_password?(password)
244 244 end
245 245
246 246 def test_create_and_continue
247 247 post :create, :params => {
248 248 :user => {
249 249 :login => 'randompass',
250 250 :firstname => 'Random',
251 251 :lastname => 'Pass',
252 252 :mail => 'randompass@example.net',
253 253 :generate_password => '1'
254 254 },
255 255 :continue => '1'
256 256 }
257 257 assert_redirected_to '/users/new?user%5Bgenerate_password%5D=1'
258 258 end
259 259
260 260 def test_create_with_failure
261 261 assert_no_difference 'User.count' do
262 262 post :create, :params => {:user => {}}
263 263 end
264 264 assert_response :success
265 265 assert_select_error /Email cannot be blank/
266 266 end
267 267
268 268 def test_create_with_failure_sould_preserve_preference
269 269 assert_no_difference 'User.count' do
270 270 post :create, :params => {
271 271 :user => {},
272 272 :pref => {
273 273 'no_self_notified' => '1',
274 274 'hide_mail' => '1',
275 275 'time_zone' => 'Paris',
276 276 'comments_sorting' => 'desc',
277 277 'warn_on_leaving_unsaved' => '0'
278 278 }
279 279 }
280 280 end
281 281 assert_response :success
282 282
283 283 assert_select 'select#pref_time_zone option[selected=selected]', :text => /Paris/
284 284 assert_select 'input#pref_no_self_notified[value="1"][checked=checked]'
285 285 end
286 286
287 287 def test_create_admin_should_send_security_notification
288 288 ActionMailer::Base.deliveries.clear
289 289 post :create, :params => {
290 290 :user => {
291 291 :firstname => 'Edgar',
292 292 :lastname => 'Schmoe',
293 293 :login => 'eschmoe',
294 294 :password => 'secret123',
295 295 :password_confirmation => 'secret123',
296 296 :mail => 'eschmoe@example.foo',
297 297 :admin => '1'
298 298 }
299 299 }
300 300
301 301 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
302 302 assert_mail_body_match '0.0.0.0', mail
303 303 assert_mail_body_match I18n.t(:mail_body_security_notification_add, field: I18n.t(:field_admin), value: 'eschmoe'), mail
304 304 assert_select_email do
305 305 assert_select 'a[href^=?]', 'http://localhost:3000/users', :text => 'Users'
306 306 end
307 307
308 308 # All admins should receive this
309 309 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
310 310 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
311 311 end
312 312 end
313 313
314 314 def test_create_non_admin_should_not_send_security_notification
315 315 ActionMailer::Base.deliveries.clear
316 316 post :create, :params => {
317 317 :user => {
318 318 :firstname => 'Edgar',
319 319 :lastname => 'Schmoe',
320 320 :login => 'eschmoe',
321 321 :password => 'secret123',
322 322 :password_confirmation => 'secret123',
323 323 :mail => 'eschmoe@example.foo',
324 324 :admin => '0'
325 325 }
326 326 }
327 327 assert_nil ActionMailer::Base.deliveries.last
328 328 end
329 329
330 330
331 331 def test_edit
332 332 get :edit, :params => {:id => 2}
333 333 assert_response :success
334 334 assert_select 'input[name=?][value=?]', 'user[login]', 'jsmith'
335 335 end
336 336
337 337 def test_edit_registered_user
338 338 assert User.find(2).register!
339 339
340 340 get :edit, :params => {:id => 2}
341 341 assert_response :success
342 342 assert_select 'a', :text => 'Activate'
343 343 end
344 344
345 345 def test_update
346 346 ActionMailer::Base.deliveries.clear
347 347 put :update, :params => {
348 348 :id => 2,
349 349 :user => {:firstname => 'Changed', :mail_notification => 'only_assigned'},
350 350 :pref => {:hide_mail => '1', :comments_sorting => 'desc'}
351 351 }
352 352 user = User.find(2)
353 353 assert_equal 'Changed', user.firstname
354 354 assert_equal 'only_assigned', user.mail_notification
355 355 assert_equal true, user.pref[:hide_mail]
356 356 assert_equal 'desc', user.pref[:comments_sorting]
357 357 assert ActionMailer::Base.deliveries.empty?
358 358 end
359 359
360 360 def test_update_with_failure
361 361 assert_no_difference 'User.count' do
362 362 put :update, :params => {
363 363 :id => 2,
364 364 :user => {:firstname => ''}
365 365 }
366 366 end
367 367 assert_response :success
368 368 assert_select_error /First name cannot be blank/
369 369 end
370 370
371 371 def test_update_with_group_ids_should_assign_groups
372 372 put :update, :params => {
373 373 :id => 2,
374 374 :user => {:group_ids => ['10']}
375 375 }
376 376 user = User.find(2)
377 377 assert_equal [10], user.group_ids
378 378 end
379 379
380 380 def test_update_with_activation_should_send_a_notification
381 381 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
382 382 u.login = 'foo'
383 383 u.status = User::STATUS_REGISTERED
384 384 u.save!
385 385 ActionMailer::Base.deliveries.clear
386 386 Setting.bcc_recipients = '1'
387 387
388 388 put :update, :params => {
389 389 :id => u.id,
390 390 :user => {:status => User::STATUS_ACTIVE}
391 391 }
392 392 assert u.reload.active?
393 393 mail = ActionMailer::Base.deliveries.last
394 394 assert_not_nil mail
395 395 assert_equal ['foo.bar@somenet.foo'], mail.bcc
396 396 assert_mail_body_match ll('fr', :notice_account_activated), mail
397 397 end
398 398
399 399 def test_update_with_password_change_should_send_a_notification
400 400 ActionMailer::Base.deliveries.clear
401 401 Setting.bcc_recipients = '1'
402 402
403 403 put :update, :params => {
404 404 :id => 2,
405 405 :user => {:password => 'newpass123', :password_confirmation => 'newpass123'},
406 406 :send_information => '1'
407 407 }
408 408 u = User.find(2)
409 409 assert u.check_password?('newpass123')
410 410
411 411 mail = ActionMailer::Base.deliveries.last
412 412 assert_not_nil mail
413 413 assert_equal [u.mail], mail.bcc
414 414 assert_mail_body_match 'newpass123', mail
415 415 end
416 416
417 417 def test_update_with_generate_password_should_email_the_password
418 418 ActionMailer::Base.deliveries.clear
419 419 Setting.bcc_recipients = '1'
420 420
421 421 put :update, :params => {
422 422 :id => 2,
423 423 :user => {
424 424 :generate_password => '1',
425 425 :password => '',
426 426 :password_confirmation => ''
427 427 },
428 428 :send_information => '1'
429 429 }
430 430
431 431 mail = ActionMailer::Base.deliveries.last
432 432 assert_not_nil mail
433 433 m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/)
434 434 assert m
435 435 password = m[1]
436 436 assert User.find(2).check_password?(password)
437 437 end
438 438
439 439 def test_update_without_generate_password_should_not_change_password
440 440 put :update, :params => {
441 441 :id => 2, :user => {
442 442 :firstname => 'changed',
443 443 :generate_password => '0',
444 444 :password => '',
445 445 :password_confirmation => ''
446 446 },
447 447 :send_information => '1'
448 448 }
449 449
450 450 user = User.find(2)
451 451 assert_equal 'changed', user.firstname
452 452 assert user.check_password?('jsmith')
453 453 end
454 454
455 455 def test_update_user_switchin_from_auth_source_to_password_authentication
456 456 # Configure as auth source
457 457 u = User.find(2)
458 458 u.auth_source = AuthSource.find(1)
459 459 u.save!
460 460
461 461 put :update, :params => {
462 462 :id => u.id,
463 463 :user => {:auth_source_id => '', :password => 'newpass123', :password_confirmation => 'newpass123'}
464 464 }
465 465
466 assert_equal nil, u.reload.auth_source
466 assert_nil u.reload.auth_source
467 467 assert u.check_password?('newpass123')
468 468 end
469 469
470 470 def test_update_notified_project
471 471 get :edit, :params => {:id => 2}
472 472 assert_response :success
473 473 u = User.find(2)
474 474 assert_equal [1, 2, 5], u.projects.collect{|p| p.id}.sort
475 475 assert_equal [1, 2, 5], u.notified_projects_ids.sort
476 476 assert_select 'input[name=?][value=?]', 'user[notified_project_ids][]', '1'
477 477 assert_equal 'all', u.mail_notification
478 478 put :update, :params => {
479 479 :id => 2,
480 480 :user => {
481 481 :mail_notification => 'selected',
482 482 :notified_project_ids => [1, 2]
483 483 }
484 484 }
485 485 u = User.find(2)
486 486 assert_equal 'selected', u.mail_notification
487 487 assert_equal [1, 2], u.notified_projects_ids.sort
488 488 end
489 489
490 490 def test_update_status_should_not_update_attributes
491 491 user = User.find(2)
492 492 user.pref[:no_self_notified] = '1'
493 493 user.pref.save
494 494
495 495 put :update, :params => {
496 496 :id => 2,
497 497 :user => {:status => 3}
498 498 }
499 499 assert_response 302
500 500 user = User.find(2)
501 501 assert_equal 3, user.status
502 502 assert_equal '1', user.pref[:no_self_notified]
503 503 end
504 504
505 505 def test_update_assign_admin_should_send_security_notification
506 506 ActionMailer::Base.deliveries.clear
507 507 put :update, :params => {
508 508 :id => 2,
509 509 :user => {:admin => 1}
510 510 }
511 511
512 512 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
513 513 assert_mail_body_match I18n.t(:mail_body_security_notification_add, field: I18n.t(:field_admin), value: User.find(2).login), mail
514 514
515 515 # All admins should receive this
516 516 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
517 517 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
518 518 end
519 519 end
520 520
521 521 def test_update_unassign_admin_should_send_security_notification
522 522 user = User.find(2)
523 523 user.admin = true
524 524 user.save!
525 525
526 526 ActionMailer::Base.deliveries.clear
527 527 put :update, :params => {
528 528 :id => user.id,
529 529 :user => {:admin => 0}
530 530 }
531 531
532 532 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
533 533 assert_mail_body_match I18n.t(:mail_body_security_notification_remove, field: I18n.t(:field_admin), value: user.login), mail
534 534
535 535 # All admins should receive this
536 536 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
537 537 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
538 538 end
539 539 end
540 540
541 541 def test_update_lock_admin_should_send_security_notification
542 542 user = User.find(2)
543 543 user.admin = true
544 544 user.save!
545 545
546 546 ActionMailer::Base.deliveries.clear
547 547 put :update, :params => {
548 548 :id => 2,
549 549 :user => {:status => Principal::STATUS_LOCKED}
550 550 }
551 551
552 552 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
553 553 assert_mail_body_match I18n.t(:mail_body_security_notification_remove, field: I18n.t(:field_admin), value: User.find(2).login), mail
554 554
555 555 # All admins should receive this
556 556 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
557 557 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
558 558 end
559 559
560 560 # if user is already locked, destroying should not send a second mail
561 561 # (for active admins see furtherbelow)
562 562 ActionMailer::Base.deliveries.clear
563 563 delete :destroy, :params => {:id => 1}
564 564 assert_nil ActionMailer::Base.deliveries.last
565 565
566 566 end
567 567
568 568 def test_update_unlock_admin_should_send_security_notification
569 569 user = User.find(5) # already locked
570 570 user.admin = true
571 571 user.save!
572 572 ActionMailer::Base.deliveries.clear
573 573 put :update, :params => {
574 574 :id => user.id,
575 575 :user => {:status => Principal::STATUS_ACTIVE}
576 576 }
577 577
578 578 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
579 579 assert_mail_body_match I18n.t(:mail_body_security_notification_add, field: I18n.t(:field_admin), value: user.login), mail
580 580
581 581 # All admins should receive this
582 582 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
583 583 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
584 584 end
585 585 end
586 586
587 587 def test_update_admin_unrelated_property_should_not_send_security_notification
588 588 ActionMailer::Base.deliveries.clear
589 589 put :update, :params => {
590 590 :id => 1,
591 591 :user => {:firstname => 'Jimmy'}
592 592 }
593 593 assert_nil ActionMailer::Base.deliveries.last
594 594 end
595 595
596 596 def test_destroy
597 597 assert_difference 'User.count', -1 do
598 598 delete :destroy, :params => {:id => 2}
599 599 end
600 600 assert_redirected_to '/users'
601 601 assert_nil User.find_by_id(2)
602 602 end
603 603
604 604 def test_destroy_should_be_denied_for_non_admin_users
605 605 @request.session[:user_id] = 3
606 606
607 607 assert_no_difference 'User.count' do
608 608 get :destroy, :params => {:id => 2}
609 609 end
610 610 assert_response 403
611 611 end
612 612
613 613 def test_destroy_should_redirect_to_back_url_param
614 614 assert_difference 'User.count', -1 do
615 615 delete :destroy, :params => {:id => 2, :back_url => '/users?name=foo'}
616 616 end
617 617 assert_redirected_to '/users?name=foo'
618 618 end
619 619
620 620 def test_destroy_active_admin_should_send_security_notification
621 621 user = User.find(2)
622 622 user.admin = true
623 623 user.save!
624 624 ActionMailer::Base.deliveries.clear
625 625 delete :destroy, :params => {:id => user.id}
626 626
627 627 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
628 628 assert_mail_body_match I18n.t(:mail_body_security_notification_remove, field: I18n.t(:field_admin), value: user.login), mail
629 629
630 630 # All admins should receive this
631 631 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
632 632 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
633 633 end
634 634 end
635 635 end
@@ -1,61 +1,61
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class AdminTest < Redmine::IntegrationTest
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :enumerations, :users, :issue_categories,
23 23 :projects_trackers,
24 24 :roles,
25 25 :member_roles,
26 26 :members,
27 27 :enabled_modules
28 28
29 29 def test_add_user
30 30 log_user("admin", "admin")
31 31 get "/users/new"
32 32 assert_response :success
33 33
34 34 post "/users",
35 35 :user => { :login => "psmith", :firstname => "Paul",
36 36 :lastname => "Smith", :mail => "psmith@somenet.foo",
37 37 :language => "en", :password => "psmith09",
38 38 :password_confirmation => "psmith09" }
39 39
40 40 user = User.find_by_login("psmith")
41 41 assert_kind_of User, user
42 42 assert_redirected_to "/users/#{ user.id }/edit"
43 43
44 44 logged_user = User.try_to_login("psmith", "psmith09")
45 45 assert_kind_of User, logged_user
46 46 assert_equal "Paul", logged_user.firstname
47 47
48 48 put "/users/#{user.id}", :id => user.id, :user => { :status => User::STATUS_LOCKED }
49 49 assert_redirected_to "/users/#{ user.id }/edit"
50 50 locked_user = User.try_to_login("psmith", "psmith09")
51 assert_equal nil, locked_user
51 assert_nil locked_user
52 52 end
53 53
54 54 test "Add a user as an anonymous user should fail" do
55 55 post '/users',
56 56 :user => { :login => 'psmith', :firstname => 'Paul'},
57 57 :password => "psmith09", :password_confirmation => "psmith09"
58 58 assert_response :redirect
59 59 assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fusers"
60 60 end
61 61 end
@@ -1,882 +1,882
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
21 21 fixtures :projects,
22 22 :users,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
26 26 :issues,
27 27 :issue_statuses,
28 28 :issue_relations,
29 29 :versions,
30 30 :trackers,
31 31 :projects_trackers,
32 32 :issue_categories,
33 33 :enabled_modules,
34 34 :enumerations,
35 35 :attachments,
36 36 :workflows,
37 37 :custom_fields,
38 38 :custom_values,
39 39 :custom_fields_projects,
40 40 :custom_fields_trackers,
41 41 :time_entries,
42 42 :journals,
43 43 :journal_details,
44 44 :queries,
45 45 :attachments
46 46
47 47 test "GET /issues.xml should contain metadata" do
48 48 get '/issues.xml'
49 49 assert_select 'issues[type=array][total_count][limit="25"][offset="0"]'
50 50 end
51 51
52 52 test "GET /issues.xml with nometa param should not contain metadata" do
53 53 get '/issues.xml?nometa=1'
54 54 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
55 55 end
56 56
57 57 test "GET /issues.xml with nometa header should not contain metadata" do
58 58 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
59 59 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
60 60 end
61 61
62 62 test "GET /issues.xml with offset and limit" do
63 63 get '/issues.xml?offset=2&limit=3'
64 64 assert_select 'issues[type=array][total_count][limit="3"][offset="2"]'
65 65 assert_select 'issues issue', 3
66 66 end
67 67
68 68 test "GET /issues.xml with relations" do
69 69 get '/issues.xml?include=relations'
70 70
71 71 assert_response :success
72 72 assert_equal 'application/xml', @response.content_type
73 73
74 74 assert_select 'issue id', :text => '3' do
75 75 assert_select '~ relations relation', 1
76 76 assert_select '~ relations relation[id="2"][issue_id="2"][issue_to_id="3"][relation_type=relates]'
77 77 end
78 78
79 79 assert_select 'issue id', :text => '1' do
80 80 assert_select '~ relations'
81 81 assert_select '~ relations relation', 0
82 82 end
83 83 end
84 84
85 85 test "GET /issues.xml with invalid query params" do
86 86 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
87 87
88 88 assert_response :unprocessable_entity
89 89 assert_equal 'application/xml', @response.content_type
90 90 assert_select 'errors error', :text => "Start date cannot be blank"
91 91 end
92 92
93 93 test "GET /issues.xml with custom field filter" do
94 94 get '/issues.xml',
95 95 {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}}
96 96
97 97 expected_ids = Issue.visible.
98 98 joins(:custom_values).
99 99 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
100 100 assert expected_ids.any?
101 101
102 102 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
103 103 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
104 104 end
105 105 end
106 106
107 107 test "GET /issues.xml with custom field filter (shorthand method)" do
108 108 get '/issues.xml', {:cf_1 => 'MySQL'}
109 109
110 110 expected_ids = Issue.visible.
111 111 joins(:custom_values).
112 112 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
113 113 assert expected_ids.any?
114 114
115 115 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
116 116 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
117 117 end
118 118 end
119 119
120 120 def test_index_should_include_issue_attributes
121 121 get '/issues.xml'
122 122 assert_select 'issues>issue>is_private', :text => 'false'
123 123 end
124 124
125 125 def test_index_should_allow_timestamp_filtering
126 126 Issue.delete_all
127 127 Issue.generate!(:subject => '1').update_column(:updated_on, Time.parse("2014-01-02T10:25:00Z"))
128 128 Issue.generate!(:subject => '2').update_column(:updated_on, Time.parse("2014-01-02T12:13:00Z"))
129 129
130 130 get '/issues.xml',
131 131 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '<='},
132 132 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
133 133 assert_select 'issues>issue', :count => 1
134 134 assert_select 'issues>issue>subject', :text => '1'
135 135
136 136 get '/issues.xml',
137 137 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
138 138 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
139 139 assert_select 'issues>issue', :count => 1
140 140 assert_select 'issues>issue>subject', :text => '2'
141 141
142 142 get '/issues.xml',
143 143 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
144 144 :v => {:updated_on => ['2014-01-02T08:00:00Z']}}
145 145 assert_select 'issues>issue', :count => 2
146 146 end
147 147
148 148 test "GET /issues.xml with filter" do
149 149 get '/issues.xml?status_id=5'
150 150
151 151 expected_ids = Issue.visible.where(:status_id => 5).map(&:id)
152 152 assert expected_ids.any?
153 153
154 154 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
155 155 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
156 156 end
157 157 end
158 158
159 159 test "GET /issues.json with filter" do
160 160 get '/issues.json?status_id=5'
161 161
162 162 json = ActiveSupport::JSON.decode(response.body)
163 163 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
164 164 assert_equal 3, status_ids_used.length
165 165 assert status_ids_used.all? {|id| id == 5 }
166 166 end
167 167
168 168 test "GET /issues/:id.xml with journals" do
169 169 Journal.find(2).update_attribute(:private_notes, true)
170 170
171 171 get '/issues/1.xml?include=journals', {}, credentials('jsmith')
172 172
173 173 assert_select 'issue journals[type=array]' do
174 174 assert_select 'journal[id="1"]' do
175 175 assert_select 'private_notes', :text => 'false'
176 176 assert_select 'details[type=array]' do
177 177 assert_select 'detail[name=status_id]' do
178 178 assert_select 'old_value', :text => '1'
179 179 assert_select 'new_value', :text => '2'
180 180 end
181 181 end
182 182 end
183 183 assert_select 'journal[id="2"]' do
184 184 assert_select 'private_notes', :text => 'true'
185 185 assert_select 'details[type=array]'
186 186 end
187 187 end
188 188 end
189 189
190 190 test "GET /issues/:id.xml with journals should format timestamps in ISO 8601" do
191 191 get '/issues/1.xml?include=journals'
192 192
193 193 iso_date = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/
194 194 assert_select 'issue>created_on', :text => iso_date
195 195 assert_select 'issue>updated_on', :text => iso_date
196 196 assert_select 'issue journal>created_on', :text => iso_date
197 197 end
198 198
199 199 test "GET /issues/:id.xml with custom fields" do
200 200 get '/issues/3.xml'
201 201
202 202 assert_select 'issue custom_fields[type=array]' do
203 203 assert_select 'custom_field[id="1"]' do
204 204 assert_select 'value', :text => 'MySQL'
205 205 end
206 206 end
207 207 assert_nothing_raised do
208 208 Hash.from_xml(response.body).to_xml
209 209 end
210 210 end
211 211
212 212 test "GET /issues/:id.xml with multi custom fields" do
213 213 field = CustomField.find(1)
214 214 field.update_attribute :multiple, true
215 215 issue = Issue.find(3)
216 216 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
217 217 issue.save!
218 218
219 219 get '/issues/3.xml'
220 220 assert_response :success
221 221
222 222 assert_select 'issue custom_fields[type=array]' do
223 223 assert_select 'custom_field[id="1"]' do
224 224 assert_select 'value[type=array] value', 2
225 225 end
226 226 end
227 227 xml = Hash.from_xml(response.body)
228 228 custom_fields = xml['issue']['custom_fields']
229 229 assert_kind_of Array, custom_fields
230 230 field = custom_fields.detect {|f| f['id'] == '1'}
231 231 assert_kind_of Hash, field
232 232 assert_equal ['MySQL', 'Oracle'], field['value'].sort
233 233 end
234 234
235 235 test "GET /issues/:id.json with multi custom fields" do
236 236 field = CustomField.find(1)
237 237 field.update_attribute :multiple, true
238 238 issue = Issue.find(3)
239 239 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
240 240 issue.save!
241 241
242 242 get '/issues/3.json'
243 243 assert_response :success
244 244
245 245 json = ActiveSupport::JSON.decode(response.body)
246 246 custom_fields = json['issue']['custom_fields']
247 247 assert_kind_of Array, custom_fields
248 248 field = custom_fields.detect {|f| f['id'] == 1}
249 249 assert_kind_of Hash, field
250 250 assert_equal ['MySQL', 'Oracle'], field['value'].sort
251 251 end
252 252
253 253 test "GET /issues/:id.xml with empty value for multi custom field" do
254 254 field = CustomField.find(1)
255 255 field.update_attribute :multiple, true
256 256 issue = Issue.find(3)
257 257 issue.custom_field_values = {1 => ['']}
258 258 issue.save!
259 259
260 260 get '/issues/3.xml'
261 261
262 262 assert_select 'issue custom_fields[type=array]' do
263 263 assert_select 'custom_field[id="1"]' do
264 264 assert_select 'value[type=array]:empty'
265 265 end
266 266 end
267 267 xml = Hash.from_xml(response.body)
268 268 custom_fields = xml['issue']['custom_fields']
269 269 assert_kind_of Array, custom_fields
270 270 field = custom_fields.detect {|f| f['id'] == '1'}
271 271 assert_kind_of Hash, field
272 272 assert_equal [], field['value']
273 273 end
274 274
275 275 test "GET /issues/:id.json with empty value for multi custom field" do
276 276 field = CustomField.find(1)
277 277 field.update_attribute :multiple, true
278 278 issue = Issue.find(3)
279 279 issue.custom_field_values = {1 => ['']}
280 280 issue.save!
281 281
282 282 get '/issues/3.json'
283 283 assert_response :success
284 284 json = ActiveSupport::JSON.decode(response.body)
285 285 custom_fields = json['issue']['custom_fields']
286 286 assert_kind_of Array, custom_fields
287 287 field = custom_fields.detect {|f| f['id'] == 1}
288 288 assert_kind_of Hash, field
289 289 assert_equal [], field['value'].sort
290 290 end
291 291
292 292 test "GET /issues/:id.xml with attachments" do
293 293 get '/issues/3.xml?include=attachments'
294 294
295 295 assert_select 'issue attachments[type=array]' do
296 296 assert_select 'attachment', 4
297 297 assert_select 'attachment id', :text => '1' do
298 298 assert_select '~ filename', :text => 'error281.txt'
299 299 assert_select '~ content_url', :text => 'http://www.example.com/attachments/download/1/error281.txt'
300 300 end
301 301 end
302 302 end
303 303
304 304 test "GET /issues/:id.xml with subtasks" do
305 305 issue = Issue.generate_with_descendants!(:project_id => 1)
306 306 get "/issues/#{issue.id}.xml?include=children"
307 307
308 308 assert_select 'issue id', :text => issue.id.to_s do
309 309 assert_select '~ children[type=array] > issue', 2
310 310 assert_select '~ children[type=array] > issue > children', 1
311 311 end
312 312 end
313 313
314 314 test "GET /issues/:id.json with subtasks" do
315 315 issue = Issue.generate_with_descendants!(:project_id => 1)
316 316 get "/issues/#{issue.id}.json?include=children"
317 317
318 318 json = ActiveSupport::JSON.decode(response.body)
319 319 assert_equal 2, json['issue']['children'].size
320 320 assert_equal 1, json['issue']['children'].select {|child| child.key?('children')}.size
321 321 end
322 322
323 323 def test_show_should_include_issue_attributes
324 324 get '/issues/1.xml'
325 325 assert_select 'issue>is_private', :text => 'false'
326 326 end
327 327
328 328 test "GET /issues/:id.xml?include=watchers should include watchers" do
329 329 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
330 330
331 331 get '/issues/1.xml?include=watchers', {}, credentials('jsmith')
332 332
333 333 assert_response :ok
334 334 assert_equal 'application/xml', response.content_type
335 335 assert_select 'issue' do
336 336 assert_select 'watchers', Issue.find(1).watchers.count
337 337 assert_select 'watchers' do
338 338 assert_select 'user[id="3"]'
339 339 end
340 340 end
341 341 end
342 342
343 343 test "GET /issues/:id.xml should not disclose associated changesets from projects the user has no access to" do
344 344 project = Project.generate!(:is_public => false)
345 345 repository = Repository::Subversion.create!(:project => project, :url => "svn://localhost")
346 346 Issue.find(1).changesets << Changeset.generate!(:repository => repository)
347 347 assert Issue.find(1).changesets.any?
348 348
349 349 get '/issues/1.xml?include=changesets', {}, credentials('jsmith')
350 350
351 351 # the user jsmith has no permission to view the associated changeset
352 352 assert_select 'issue changesets[type=array]' do
353 353 assert_select 'changeset', 0
354 354 end
355 355 end
356 356
357 357 test "GET /issues/:id.xml should contains total_estimated_hours and total_spent_hours" do
358 358 parent = Issue.find(3)
359 359 child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
360 360 TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
361 361 :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
362 362 get '/issues/3.xml'
363 363
364 364 assert_equal 'application/xml', response.content_type
365 365 assert_select 'issue' do
366 366 assert_select 'estimated_hours', parent.estimated_hours.to_s
367 367 assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s
368 368 assert_select 'spent_hours', parent.spent_hours.to_s
369 369 assert_select 'total_spent_hours', (parent.spent_hours.to_f + 2.5).to_s
370 370 end
371 371 end
372 372
373 373 test "GET /issues/:id.xml should contains total_estimated_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
374 374 parent = Issue.find(3)
375 375 child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
376 376 # remove permission!
377 377 Role.anonymous.remove_permission! :view_time_entries
378 378 #Role.all.each { |role| role.remove_permission! :view_time_entries }
379 379 get '/issues/3.xml'
380 380
381 381 assert_equal 'application/xml', response.content_type
382 382 assert_select 'issue' do
383 383 assert_select 'estimated_hours', parent.estimated_hours.to_s
384 384 assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s
385 385 assert_select 'spent_hours', false
386 386 assert_select 'total_spent_hours', false
387 387 end
388 388 end
389 389
390 390 test "GET /issues/:id.json should contains total_estimated_hours and total_spent_hours" do
391 391 parent = Issue.find(3)
392 392 child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
393 393 TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today,
394 394 :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id)
395 395 get '/issues/3.json'
396 396
397 397 assert_equal 'application/json', response.content_type
398 398 json = ActiveSupport::JSON.decode(response.body)
399 399 assert_equal parent.estimated_hours, json['issue']['estimated_hours']
400 400 assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours']
401 401 assert_equal parent.spent_hours, json['issue']['spent_hours']
402 402 assert_equal (parent.spent_hours.to_f + 2.5), json['issue']['total_spent_hours']
403 403 end
404 404
405 405 test "GET /issues/:id.json should contains total_estimated_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
406 406 parent = Issue.find(3)
407 407 child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0)
408 408 # remove permission!
409 409 Role.anonymous.remove_permission! :view_time_entries
410 410 #Role.all.each { |role| role.remove_permission! :view_time_entries }
411 411 get '/issues/3.json'
412 412
413 413 assert_equal 'application/json', response.content_type
414 414 json = ActiveSupport::JSON.decode(response.body)
415 415 assert_equal parent.estimated_hours, json['issue']['estimated_hours']
416 416 assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours']
417 assert_equal nil, json['issue']['spent_hours']
418 assert_equal nil, json['issue']['total_spent_hours']
417 assert_nil json['issue']['spent_hours']
418 assert_nil json['issue']['total_spent_hours']
419 419 end
420 420
421 421 test "POST /issues.xml should create an issue with the attributes" do
422 422
423 423 payload = <<-XML
424 424 <?xml version="1.0" encoding="UTF-8" ?>
425 425 <issue>
426 426 <project_id>1</project_id>
427 427 <tracker_id>2</tracker_id>
428 428 <status_id>3</status_id>
429 429 <subject>API test</subject>
430 430 </issue>
431 431 XML
432 432
433 433 assert_difference('Issue.count') do
434 434 post '/issues.xml', payload, {"CONTENT_TYPE" => 'application/xml'}.merge(credentials('jsmith'))
435 435 end
436 436 issue = Issue.order('id DESC').first
437 437 assert_equal 1, issue.project_id
438 438 assert_equal 2, issue.tracker_id
439 439 assert_equal 3, issue.status_id
440 440 assert_equal 'API test', issue.subject
441 441
442 442 assert_response :created
443 443 assert_equal 'application/xml', @response.content_type
444 444 assert_select 'issue > id', :text => issue.id.to_s
445 445 end
446 446
447 447 test "POST /issues.xml with watcher_user_ids should create issue with watchers" do
448 448 assert_difference('Issue.count') do
449 449 post '/issues.xml',
450 450 {:issue => {:project_id => 1, :subject => 'Watchers',
451 451 :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith')
452 452 assert_response :created
453 453 end
454 454 issue = Issue.order('id desc').first
455 455 assert_equal 2, issue.watchers.size
456 456 assert_equal [1, 3], issue.watcher_user_ids.sort
457 457 end
458 458
459 459 test "POST /issues.xml with failure should return errors" do
460 460 assert_no_difference('Issue.count') do
461 461 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
462 462 end
463 463
464 464 assert_select 'errors error', :text => "Subject cannot be blank"
465 465 end
466 466
467 467 test "POST /issues.json should create an issue with the attributes" do
468 468
469 469 payload = <<-JSON
470 470 {
471 471 "issue": {
472 472 "project_id": "1",
473 473 "tracker_id": "2",
474 474 "status_id": "3",
475 475 "subject": "API test"
476 476 }
477 477 }
478 478 JSON
479 479
480 480 assert_difference('Issue.count') do
481 481 post '/issues.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
482 482 end
483 483
484 484 issue = Issue.order('id DESC').first
485 485 assert_equal 1, issue.project_id
486 486 assert_equal 2, issue.tracker_id
487 487 assert_equal 3, issue.status_id
488 488 assert_equal 'API test', issue.subject
489 489 end
490 490
491 491 test "POST /issues.json should accept project identifier as project_id" do
492 492 assert_difference('Issue.count') do
493 493 post '/issues.json',
494 494 {:issue => {:project_id => 'subproject1', :tracker_id => 2, :subject => 'Foo'}},
495 495 credentials('jsmith')
496 496
497 497 assert_response :created
498 498 end
499 499 end
500 500
501 501 test "POST /issues.json without tracker_id should accept custom fields" do
502 502 field = IssueCustomField.generate!(
503 503 :field_format => 'list',
504 504 :multiple => true,
505 505 :possible_values => ["V1", "V2", "V3"],
506 506 :default_value => "V2",
507 507 :is_for_all => true,
508 508 :trackers => Tracker.all.to_a
509 509 )
510 510
511 511 payload = <<-JSON
512 512 {
513 513 "issue": {
514 514 "project_id": "1",
515 515 "subject": "Multivalued custom field",
516 516 "custom_field_values":{"#{field.id}":["V1","V3"]}
517 517 }
518 518 }
519 519 JSON
520 520
521 521 assert_difference('Issue.count') do
522 522 post '/issues.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
523 523 end
524 524
525 525 assert_response :created
526 526 issue = Issue.order('id DESC').first
527 527 assert_equal ["V1", "V3"], issue.custom_field_value(field).sort
528 528 end
529 529
530 530 test "POST /issues.json with omitted custom field should set default value" do
531 531 field = IssueCustomField.generate!(:default_value => "Default")
532 532
533 533 issue = new_record(Issue) do
534 534 post '/issues.json',
535 535 {:issue => {:project_id => 1, :subject => 'API', :custom_field_values => {}}},
536 536 credentials('jsmith')
537 537 end
538 538 assert_equal "Default", issue.custom_field_value(field)
539 539 end
540 540
541 541 test "POST /issues.json with custom field set to blank should not set default value" do
542 542 field = IssueCustomField.generate!(:default_value => "Default")
543 543
544 544 issue = new_record(Issue) do
545 545 post '/issues.json',
546 546 {:issue => {:project_id => 1, :subject => 'API', :custom_field_values => {field.id.to_s => ""}}},
547 547 credentials('jsmith')
548 548 end
549 549 assert_equal "", issue.custom_field_value(field)
550 550 end
551 551
552 552 test "POST /issues.json with failure should return errors" do
553 553 assert_no_difference('Issue.count') do
554 554 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
555 555 end
556 556
557 557 json = ActiveSupport::JSON.decode(response.body)
558 558 assert json['errors'].include?("Subject cannot be blank")
559 559 end
560 560
561 561 test "POST /issues.json with invalid project_id should respond with 422" do
562 562 post '/issues.json', {:issue => {:project_id => 999, :subject => "API"}}, credentials('jsmith')
563 563 assert_response 422
564 564 end
565 565
566 566 test "PUT /issues/:id.xml" do
567 567 assert_difference('Journal.count') do
568 568 put '/issues/6.xml',
569 569 {:issue => {:subject => 'API update', :notes => 'A new note'}},
570 570 credentials('jsmith')
571 571 end
572 572
573 573 issue = Issue.find(6)
574 574 assert_equal "API update", issue.subject
575 575 journal = Journal.last
576 576 assert_equal "A new note", journal.notes
577 577 end
578 578
579 579 test "PUT /issues/:id.xml with custom fields" do
580 580 put '/issues/3.xml',
581 581 {:issue => {:custom_fields => [
582 582 {'id' => '1', 'value' => 'PostgreSQL' },
583 583 {'id' => '2', 'value' => '150'}
584 584 ]}},
585 585 credentials('jsmith')
586 586
587 587 issue = Issue.find(3)
588 588 assert_equal '150', issue.custom_value_for(2).value
589 589 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
590 590 end
591 591
592 592 test "PUT /issues/:id.xml with multi custom fields" do
593 593 field = CustomField.find(1)
594 594 field.update_attribute :multiple, true
595 595
596 596 put '/issues/3.xml',
597 597 {:issue => {:custom_fields => [
598 598 {'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] },
599 599 {'id' => '2', 'value' => '150'}
600 600 ]}},
601 601 credentials('jsmith')
602 602
603 603 issue = Issue.find(3)
604 604 assert_equal '150', issue.custom_value_for(2).value
605 605 assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort
606 606 end
607 607
608 608 test "PUT /issues/:id.xml with project change" do
609 609 put '/issues/3.xml',
610 610 {:issue => {:project_id => 2, :subject => 'Project changed'}},
611 611 credentials('jsmith')
612 612
613 613 issue = Issue.find(3)
614 614 assert_equal 2, issue.project_id
615 615 assert_equal 'Project changed', issue.subject
616 616 end
617 617
618 618 test "PUT /issues/:id.xml with notes only" do
619 619 assert_difference('Journal.count') do
620 620 put '/issues/6.xml',
621 621 {:issue => {:notes => 'Notes only'}},
622 622 credentials('jsmith')
623 623 end
624 624
625 625 journal = Journal.last
626 626 assert_equal "Notes only", journal.notes
627 627 end
628 628
629 629 test "PUT /issues/:id.json with omitted custom field should not change blank value to default value" do
630 630 field = IssueCustomField.generate!(:default_value => "Default")
631 631 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => ""})
632 632 assert_equal "", issue.reload.custom_field_value(field)
633 633
634 634 assert_difference('Journal.count') do
635 635 put "/issues/#{issue.id}.json",
636 636 {:issue => {:custom_field_values => {}, :notes => 'API'}},
637 637 credentials('jsmith')
638 638 end
639 639
640 640 assert_equal "", issue.reload.custom_field_value(field)
641 641 end
642 642
643 643 test "PUT /issues/:id.json with custom field set to blank should not change blank value to default value" do
644 644 field = IssueCustomField.generate!(:default_value => "Default")
645 645 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => ""})
646 646 assert_equal "", issue.reload.custom_field_value(field)
647 647
648 648 assert_difference('Journal.count') do
649 649 put "/issues/#{issue.id}.json",
650 650 {:issue => {:custom_field_values => {field.id.to_s => ""}, :notes => 'API'}},
651 651 credentials('jsmith')
652 652 end
653 653
654 654 assert_equal "", issue.reload.custom_field_value(field)
655 655 end
656 656
657 657 test "PUT /issues/:id.json with tracker change and omitted custom field specific to that tracker should set default value" do
658 658 field = IssueCustomField.generate!(:default_value => "Default", :tracker_ids => [2])
659 659 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
660 660
661 661 assert_difference('Journal.count') do
662 662 put "/issues/#{issue.id}.json",
663 663 {:issue => {:tracker_id => 2, :custom_field_values => {}, :notes => 'API'}},
664 664 credentials('jsmith')
665 665 end
666 666
667 667 assert_equal 2, issue.reload.tracker_id
668 668 assert_equal "Default", issue.reload.custom_field_value(field)
669 669 end
670 670
671 671 test "PUT /issues/:id.json with tracker change and custom field specific to that tracker set to blank should not set default value" do
672 672 field = IssueCustomField.generate!(:default_value => "Default", :tracker_ids => [2])
673 673 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
674 674
675 675 assert_difference('Journal.count') do
676 676 put "/issues/#{issue.id}.json",
677 677 {:issue => {:tracker_id => 2, :custom_field_values => {field.id.to_s => ""}, :notes => 'API'}},
678 678 credentials('jsmith')
679 679 end
680 680
681 681 assert_equal 2, issue.reload.tracker_id
682 682 assert_equal "", issue.reload.custom_field_value(field)
683 683 end
684 684
685 685 test "PUT /issues/:id.xml with failed update" do
686 686 put '/issues/6.xml', {:issue => {:subject => ''}}, credentials('jsmith')
687 687
688 688 assert_response :unprocessable_entity
689 689 assert_select 'errors error', :text => "Subject cannot be blank"
690 690 end
691 691
692 692 test "PUT /issues/:id.xml with invalid assignee should return error" do
693 693 user = User.generate!
694 694 put '/issues/6.xml', {:issue => {:assigned_to_id => user.id}}, credentials('jsmith')
695 695
696 696 assert_response :unprocessable_entity
697 697 assert_select 'errors error', :text => "Assignee is invalid"
698 698 end
699 699
700 700 test "PUT /issues/:id.json" do
701 701 assert_difference('Journal.count') do
702 702 put '/issues/6.json',
703 703 {:issue => {:subject => 'API update', :notes => 'A new note'}},
704 704 credentials('jsmith')
705 705
706 706 assert_response :ok
707 707 assert_equal '', response.body
708 708 end
709 709
710 710 issue = Issue.find(6)
711 711 assert_equal "API update", issue.subject
712 712 journal = Journal.last
713 713 assert_equal "A new note", journal.notes
714 714 end
715 715
716 716 test "PUT /issues/:id.json with failed update" do
717 717 put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith')
718 718
719 719 assert_response :unprocessable_entity
720 720 json = ActiveSupport::JSON.decode(response.body)
721 721 assert json['errors'].include?("Subject cannot be blank")
722 722 end
723 723
724 724 test "DELETE /issues/:id.xml" do
725 725 assert_difference('Issue.count', -1) do
726 726 delete '/issues/6.xml', {}, credentials('jsmith')
727 727
728 728 assert_response :ok
729 729 assert_equal '', response.body
730 730 end
731 731 assert_nil Issue.find_by_id(6)
732 732 end
733 733
734 734 test "DELETE /issues/:id.json" do
735 735 assert_difference('Issue.count', -1) do
736 736 delete '/issues/6.json', {}, credentials('jsmith')
737 737
738 738 assert_response :ok
739 739 assert_equal '', response.body
740 740 end
741 741 assert_nil Issue.find_by_id(6)
742 742 end
743 743
744 744 test "POST /issues/:id/watchers.xml should add watcher" do
745 745 assert_difference 'Watcher.count' do
746 746 post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith')
747 747
748 748 assert_response :ok
749 749 assert_equal '', response.body
750 750 end
751 751 watcher = Watcher.order('id desc').first
752 752 assert_equal Issue.find(1), watcher.watchable
753 753 assert_equal User.find(3), watcher.user
754 754 end
755 755
756 756 test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do
757 757 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
758 758
759 759 assert_difference 'Watcher.count', -1 do
760 760 delete '/issues/1/watchers/3.xml', {}, credentials('jsmith')
761 761
762 762 assert_response :ok
763 763 assert_equal '', response.body
764 764 end
765 765 assert_equal false, Issue.find(1).watched_by?(User.find(3))
766 766 end
767 767
768 768 def test_create_issue_with_uploaded_file
769 769 token = xml_upload('test_create_with_upload', credentials('jsmith'))
770 770 attachment = Attachment.find_by_token(token)
771 771
772 772 # create the issue with the upload's token
773 773 assert_difference 'Issue.count' do
774 774 post '/issues.xml',
775 775 {:issue => {:project_id => 1, :subject => 'Uploaded file',
776 776 :uploads => [{:token => token, :filename => 'test.txt',
777 777 :content_type => 'text/plain'}]}},
778 778 credentials('jsmith')
779 779 assert_response :created
780 780 end
781 781 issue = Issue.order('id DESC').first
782 782 assert_equal 1, issue.attachments.count
783 783 assert_equal attachment, issue.attachments.first
784 784
785 785 attachment.reload
786 786 assert_equal 'test.txt', attachment.filename
787 787 assert_equal 'text/plain', attachment.content_type
788 788 assert_equal 'test_create_with_upload'.size, attachment.filesize
789 789 assert_equal 2, attachment.author_id
790 790
791 791 # get the issue with its attachments
792 792 get "/issues/#{issue.id}.xml", :include => 'attachments'
793 793 assert_response :success
794 794 xml = Hash.from_xml(response.body)
795 795 attachments = xml['issue']['attachments']
796 796 assert_kind_of Array, attachments
797 797 assert_equal 1, attachments.size
798 798 url = attachments.first['content_url']
799 799 assert_not_nil url
800 800
801 801 # download the attachment
802 802 get url
803 803 assert_response :success
804 804 assert_equal 'test_create_with_upload', response.body
805 805 end
806 806
807 807 def test_create_issue_with_multiple_uploaded_files_as_xml
808 808 token1 = xml_upload('File content 1', credentials('jsmith'))
809 809 token2 = xml_upload('File content 2', credentials('jsmith'))
810 810
811 811 payload = <<-XML
812 812 <?xml version="1.0" encoding="UTF-8" ?>
813 813 <issue>
814 814 <project_id>1</project_id>
815 815 <tracker_id>1</tracker_id>
816 816 <subject>Issue with multiple attachments</subject>
817 817 <uploads type="array">
818 818 <upload>
819 819 <token>#{token1}</token>
820 820 <filename>test1.txt</filename>
821 821 </upload>
822 822 <upload>
823 823 <token>#{token2}</token>
824 824 <filename>test1.txt</filename>
825 825 </upload>
826 826 </uploads>
827 827 </issue>
828 828 XML
829 829
830 830 assert_difference 'Issue.count' do
831 831 post '/issues.xml', payload, {"CONTENT_TYPE" => 'application/xml'}.merge(credentials('jsmith'))
832 832 assert_response :created
833 833 end
834 834 issue = Issue.order('id DESC').first
835 835 assert_equal 2, issue.attachments.count
836 836 end
837 837
838 838 def test_create_issue_with_multiple_uploaded_files_as_json
839 839 token1 = json_upload('File content 1', credentials('jsmith'))
840 840 token2 = json_upload('File content 2', credentials('jsmith'))
841 841
842 842 payload = <<-JSON
843 843 {
844 844 "issue": {
845 845 "project_id": "1",
846 846 "tracker_id": "1",
847 847 "subject": "Issue with multiple attachments",
848 848 "uploads": [
849 849 {"token": "#{token1}", "filename": "test1.txt"},
850 850 {"token": "#{token2}", "filename": "test2.txt"}
851 851 ]
852 852 }
853 853 }
854 854 JSON
855 855
856 856 assert_difference 'Issue.count' do
857 857 post '/issues.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
858 858 assert_response :created
859 859 end
860 860 issue = Issue.order('id DESC').first
861 861 assert_equal 2, issue.attachments.count
862 862 end
863 863
864 864 def test_update_issue_with_uploaded_file
865 865 token = xml_upload('test_upload_with_upload', credentials('jsmith'))
866 866 attachment = Attachment.find_by_token(token)
867 867
868 868 # update the issue with the upload's token
869 869 assert_difference 'Journal.count' do
870 870 put '/issues/1.xml',
871 871 {:issue => {:notes => 'Attachment added',
872 872 :uploads => [{:token => token, :filename => 'test.txt',
873 873 :content_type => 'text/plain'}]}},
874 874 credentials('jsmith')
875 875 assert_response :ok
876 876 assert_equal '', @response.body
877 877 end
878 878
879 879 issue = Issue.find(1)
880 880 assert_include attachment, issue.attachments
881 881 end
882 882 end
@@ -1,234 +1,234
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class AuthSourceLdapTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 fixtures :auth_sources
23 23
24 24 def setup
25 25 end
26 26
27 27 def test_initialize
28 28 auth_source = AuthSourceLdap.new
29 29 assert_nil auth_source.id
30 30 assert_equal "AuthSourceLdap", auth_source.type
31 31 assert_equal "", auth_source.name
32 32 assert_nil auth_source.host
33 33 assert_nil auth_source.port
34 34 assert_nil auth_source.account
35 35 assert_equal "", auth_source.account_password
36 36 assert_nil auth_source.base_dn
37 37 assert_nil auth_source.attr_login
38 38 assert_nil auth_source.attr_firstname
39 39 assert_nil auth_source.attr_lastname
40 40 assert_nil auth_source.attr_mail
41 41 assert_equal false, auth_source.onthefly_register
42 42 assert_equal false, auth_source.tls
43 43 assert_nil auth_source.filter
44 44 assert_nil auth_source.timeout
45 45 end
46 46
47 47 def test_create
48 48 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName')
49 49 assert a.save
50 50 end
51 51
52 52 def test_should_strip_ldap_attributes
53 53 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName',
54 54 :attr_firstname => 'givenName ')
55 55 assert a.save
56 56 assert_equal 'givenName', a.reload.attr_firstname
57 57 end
58 58
59 59 def test_replace_port_zero_to_389
60 60 a = AuthSourceLdap.new(
61 61 :name => 'My LDAP', :host => 'ldap.example.net', :port => 0,
62 62 :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName',
63 63 :attr_firstname => 'givenName ')
64 64 assert a.save
65 65 assert_equal 389, a.port
66 66 end
67 67
68 68 def test_filter_should_be_validated
69 69 set_language_if_valid 'en'
70 70
71 71 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn')
72 72 a.filter = "(mail=*@redmine.org"
73 73 assert !a.valid?
74 74 assert_include "LDAP filter is invalid", a.errors.full_messages
75 75
76 76 a.filter = "(mail=*@redmine.org)"
77 77 assert a.valid?
78 78 end
79 79
80 80 if ldap_configured?
81 81 test '#authenticate with a valid LDAP user should return the user attributes' do
82 82 auth = AuthSourceLdap.find(1)
83 83 auth.update_attribute :onthefly_register, true
84 84
85 85 attributes = auth.authenticate('example1','123456')
86 86 assert attributes.is_a?(Hash), "An hash was not returned"
87 87 assert_equal 'Example', attributes[:firstname]
88 88 assert_equal 'One', attributes[:lastname]
89 89 assert_equal 'example1@redmine.org', attributes[:mail]
90 90 assert_equal auth.id, attributes[:auth_source_id]
91 91 attributes.keys.each do |attribute|
92 92 assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned"
93 93 end
94 94 end
95 95
96 96 test '#authenticate with an invalid LDAP user should return nil' do
97 97 auth = AuthSourceLdap.find(1)
98 assert_equal nil, auth.authenticate('nouser','123456')
98 assert_nil auth.authenticate('nouser','123456')
99 99 end
100 100
101 101 test '#authenticate without a login should return nil' do
102 102 auth = AuthSourceLdap.find(1)
103 assert_equal nil, auth.authenticate('','123456')
103 assert_nil auth.authenticate('','123456')
104 104 end
105 105
106 106 test '#authenticate without a password should return nil' do
107 107 auth = AuthSourceLdap.find(1)
108 assert_equal nil, auth.authenticate('edavis','')
108 assert_nil auth.authenticate('edavis','')
109 109 end
110 110
111 111 test '#authenticate without filter should return any user' do
112 112 auth = AuthSourceLdap.find(1)
113 113 assert auth.authenticate('example1','123456')
114 114 assert auth.authenticate('edavis', '123456')
115 115 end
116 116
117 117 test '#authenticate with filter should return user who matches the filter only' do
118 118 auth = AuthSourceLdap.find(1)
119 119 auth.filter = "(mail=*@redmine.org)"
120 120
121 121 assert auth.authenticate('example1','123456')
122 122 assert_nil auth.authenticate('edavis', '123456')
123 123 end
124 124
125 125 def test_authenticate_should_timeout
126 126 auth_source = AuthSourceLdap.find(1)
127 127 auth_source.timeout = 1
128 128 def auth_source.initialize_ldap_con(*args); sleep(5); end
129 129
130 130 assert_raise AuthSourceTimeoutException do
131 131 auth_source.authenticate 'example1', '123456'
132 132 end
133 133 end
134 134
135 135 def test_search_should_return_matching_entries
136 136 results = AuthSource.search("exa")
137 137 assert_equal 1, results.size
138 138 result = results.first
139 139 assert_kind_of Hash, result
140 140 assert_equal "example1", result[:login]
141 141 assert_equal "Example", result[:firstname]
142 142 assert_equal "One", result[:lastname]
143 143 assert_equal "example1@redmine.org", result[:mail]
144 144 assert_equal 1, result[:auth_source_id]
145 145 end
146 146
147 147 def test_search_with_no_match_should_return_an_empty_array
148 148 results = AuthSource.search("wro")
149 149 assert_equal [], results
150 150 end
151 151
152 152 def test_search_with_exception_should_return_an_empty_array
153 153 Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect')
154 154
155 155 results = AuthSource.search("exa")
156 156 assert_equal [], results
157 157 end
158 158
159 159 def test_test_connection_with_correct_host_and_port
160 160 auth_source = AuthSourceLdap.find(1)
161 161
162 162 assert_nothing_raised Net::LDAP::Error do
163 163 auth_source.test_connection
164 164 end
165 165 end
166 166
167 167 def test_test_connection_with_incorrect_host
168 168 auth_source = AuthSourceLdap.find(1)
169 169 auth_source.host = "badhost"
170 170 auth_source.save!
171 171
172 172 assert_raise Net::LDAP::Error do
173 173 auth_source.test_connection
174 174 end
175 175 end
176 176
177 177 def test_test_connection_with_incorrect_port
178 178 auth_source = AuthSourceLdap.find(1)
179 179 auth_source.port = 1234
180 180 auth_source.save!
181 181
182 182 assert_raise Net::LDAP::Error do
183 183 auth_source.test_connection
184 184 end
185 185 end
186 186
187 187 def test_test_connection_bind_with_account_and_password
188 188 auth_source = AuthSourceLdap.find(1)
189 189 auth_source.account = "cn=admin,dc=redmine,dc=org"
190 190 auth_source.account_password = "secret"
191 191 auth_source.save!
192 192
193 193 assert_equal "cn=admin,dc=redmine,dc=org", auth_source.account
194 194 assert_equal "secret", auth_source.account_password
195 195 assert_nil auth_source.test_connection
196 196 end
197 197
198 198 def test_test_connection_bind_without_account_and_password
199 199 auth_source = AuthSourceLdap.find(1)
200 200
201 201 assert_nil auth_source.account
202 202 assert_equal "", auth_source.account_password
203 203 assert_nil auth_source.test_connection
204 204 end
205 205
206 206 def test_test_connection_bind_with_incorrect_account
207 207 auth_source = AuthSourceLdap.find(1)
208 208 auth_source.account = "cn=baduser,dc=redmine,dc=org"
209 209 auth_source.account_password = "secret"
210 210 auth_source.save!
211 211
212 212 assert_equal "cn=baduser,dc=redmine,dc=org", auth_source.account
213 213 assert_equal "secret", auth_source.account_password
214 214 assert_raise AuthSourceException do
215 215 auth_source.test_connection
216 216 end
217 217 end
218 218
219 219 def test_test_connection_bind_with_incorrect_password
220 220 auth_source = AuthSourceLdap.find(1)
221 221 auth_source.account = "cn=admin,dc=redmine,dc=org"
222 222 auth_source.account_password = "badpassword"
223 223 auth_source.save!
224 224
225 225 assert_equal "cn=admin,dc=redmine,dc=org", auth_source.account
226 226 assert_equal "badpassword", auth_source.account_password
227 227 assert_raise AuthSourceException do
228 228 auth_source.test_connection
229 229 end
230 230 end
231 231 else
232 232 puts '(Test LDAP server not configured)'
233 233 end
234 234 end
@@ -1,608 +1,608
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../test_helper', __FILE__)
21 21
22 22 class ChangesetTest < ActiveSupport::TestCase
23 23 fixtures :projects, :repositories,
24 24 :issues, :issue_statuses, :issue_categories,
25 25 :journals, :journal_details,
26 26 :workflows,
27 27 :changesets, :changes,
28 28 :enumerations,
29 29 :custom_fields, :custom_values,
30 30 :users, :members, :member_roles,
31 31 :email_addresses,
32 32 :trackers, :projects_trackers,
33 33 :enabled_modules, :roles
34 34
35 35 def test_ref_keywords_any
36 36 ActionMailer::Base.deliveries.clear
37 37 Setting.commit_ref_keywords = '*'
38 38 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => '5', 'done_ratio' => '90'}]
39 39
40 40 c = Changeset.new(:repository => Project.find(1).repository,
41 41 :committed_on => Time.now,
42 42 :comments => 'New commit (#2). Fixes #1',
43 43 :revision => '12345')
44 44 assert c.save
45 45 assert_equal [1, 2], c.issue_ids.sort
46 46 fixed = Issue.find(1)
47 47 assert fixed.closed?
48 48 assert_equal 90, fixed.done_ratio
49 49 assert_equal 1, ActionMailer::Base.deliveries.size
50 50 end
51 51
52 52 def test_ref_keywords
53 53 Setting.commit_ref_keywords = 'refs'
54 54 Setting.commit_update_keywords = ''
55 55 c = Changeset.new(:repository => Project.find(1).repository,
56 56 :committed_on => Time.now,
57 57 :comments => 'Ignores #2. Refs #1',
58 58 :revision => '12345')
59 59 assert c.save
60 60 assert_equal [1], c.issue_ids.sort
61 61 end
62 62
63 63 def test_ref_keywords_any_only
64 64 Setting.commit_ref_keywords = '*'
65 65 Setting.commit_update_keywords = ''
66 66 c = Changeset.new(:repository => Project.find(1).repository,
67 67 :committed_on => Time.now,
68 68 :comments => 'Ignores #2. Refs #1',
69 69 :revision => '12345')
70 70 assert c.save
71 71 assert_equal [1, 2], c.issue_ids.sort
72 72 end
73 73
74 74 def test_ref_keywords_any_with_timelog
75 75 Setting.commit_ref_keywords = '*'
76 76 Setting.commit_logtime_enabled = '1'
77 77
78 78 {
79 79 '2' => 2.0,
80 80 '2h' => 2.0,
81 81 '2hours' => 2.0,
82 82 '15m' => 0.25,
83 83 '15min' => 0.25,
84 84 '3h15' => 3.25,
85 85 '3h15m' => 3.25,
86 86 '3h15min' => 3.25,
87 87 '3:15' => 3.25,
88 88 '3.25' => 3.25,
89 89 '3.25h' => 3.25,
90 90 '3,25' => 3.25,
91 91 '3,25h' => 3.25,
92 92 }.each do |syntax, expected_hours|
93 93 c = Changeset.new(:repository => Project.find(1).repository,
94 94 :committed_on => 24.hours.ago,
95 95 :comments => "Worked on this issue #1 @#{syntax}",
96 96 :revision => '520',
97 97 :user => User.find(2))
98 98 assert_difference 'TimeEntry.count' do
99 99 c.scan_comment_for_issue_ids
100 100 end
101 101 assert_equal [1], c.issue_ids.sort
102 102
103 103 time = TimeEntry.order('id desc').first
104 104 assert_equal 1, time.issue_id
105 105 assert_equal 1, time.project_id
106 106 assert_equal 2, time.user_id
107 107 assert_equal expected_hours, time.hours,
108 108 "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}"
109 109 assert_equal Date.yesterday, time.spent_on
110 110 assert time.activity.is_default?
111 111 assert time.comments.include?('r520'),
112 112 "r520 was expected in time_entry comments: #{time.comments}"
113 113 end
114 114 end
115 115
116 116 def test_ref_keywords_closing_with_timelog
117 117 Setting.commit_ref_keywords = '*'
118 118 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes',
119 119 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s}]
120 120 Setting.commit_logtime_enabled = '1'
121 121
122 122 c = Changeset.new(:repository => Project.find(1).repository,
123 123 :committed_on => Time.now,
124 124 :comments => 'This is a comment. Fixes #1 @4.5, #2 @1',
125 125 :user => User.find(2))
126 126 assert_difference 'TimeEntry.count', 2 do
127 127 c.scan_comment_for_issue_ids
128 128 end
129 129
130 130 assert_equal [1, 2], c.issue_ids.sort
131 131 assert Issue.find(1).closed?
132 132 assert Issue.find(2).closed?
133 133
134 134 times = TimeEntry.order('id desc').limit(2)
135 135 assert_equal [1, 2], times.collect(&:issue_id).sort
136 136 end
137 137
138 138 def test_ref_keywords_any_line_start
139 139 Setting.commit_ref_keywords = '*'
140 140 c = Changeset.new(:repository => Project.find(1).repository,
141 141 :committed_on => Time.now,
142 142 :comments => '#1 is the reason of this commit',
143 143 :revision => '12345')
144 144 assert c.save
145 145 assert_equal [1], c.issue_ids.sort
146 146 end
147 147
148 148 def test_ref_keywords_allow_brackets_around_a_issue_number
149 149 Setting.commit_ref_keywords = '*'
150 150 c = Changeset.new(:repository => Project.find(1).repository,
151 151 :committed_on => Time.now,
152 152 :comments => '[#1] Worked on this issue',
153 153 :revision => '12345')
154 154 assert c.save
155 155 assert_equal [1], c.issue_ids.sort
156 156 end
157 157
158 158 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
159 159 Setting.commit_ref_keywords = '*'
160 160 c = Changeset.new(:repository => Project.find(1).repository,
161 161 :committed_on => Time.now,
162 162 :comments => '[#1 #2, #3] Worked on these',
163 163 :revision => '12345')
164 164 assert c.save
165 165 assert_equal [1,2,3], c.issue_ids.sort
166 166 end
167 167
168 168 def test_ref_keywords_with_large_number_should_not_error
169 169 Setting.commit_ref_keywords = '*'
170 170 c = Changeset.new(:repository => Project.find(1).repository,
171 171 :committed_on => Time.now,
172 172 :comments => 'Out of range #2010021810000121',
173 173 :revision => '12345')
174 174 assert_nothing_raised do
175 175 assert c.save
176 176 end
177 177 assert_equal [], c.issue_ids.sort
178 178 end
179 179
180 180 def test_update_keywords_with_changes_should_create_journal
181 181 issue = Issue.generate!(:project_id => 1, :status_id => 1)
182 182
183 183 with_settings :commit_update_keywords => [{'keywords' => 'fixes', 'status_id' => '3'}] do
184 184 assert_difference 'Journal.count' do
185 185 c = Changeset.generate!(:repository => Project.find(1).repository,:comments => "Fixes ##{issue.id}")
186 186 assert_include c.id, issue.reload.changeset_ids
187 187 journal = Journal.order('id DESC').first
188 188 assert_equal 1, journal.details.count
189 189 end
190 190 end
191 191 end
192 192
193 193 def test_update_keywords_without_change_should_not_create_journal
194 194 issue = Issue.generate!(:project_id => 1, :status_id => 3)
195 195
196 196 with_settings :commit_update_keywords => [{'keywords' => 'fixes', 'status_id' => '3'}] do
197 197 assert_no_difference 'Journal.count' do
198 198 c = Changeset.generate!(:repository => Project.find(1).repository,:comments => "Fixes ##{issue.id}")
199 199 assert_include c.id, issue.reload.changeset_ids
200 200 end
201 201 end
202 202 end
203 203
204 204 def test_update_keywords_with_multiple_rules
205 205 with_settings :commit_update_keywords => [
206 206 {'keywords' => 'fixes, closes', 'status_id' => '5'},
207 207 {'keywords' => 'resolves', 'status_id' => '3'}
208 208 ] do
209 209
210 210 issue1 = Issue.generate!
211 211 issue2 = Issue.generate!
212 212 Changeset.generate!(:comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}")
213 213 assert_equal 5, issue1.reload.status_id
214 214 assert_equal 3, issue2.reload.status_id
215 215 end
216 216 end
217 217
218 218 def test_update_keywords_with_multiple_rules_for_the_same_keyword_should_match_tracker
219 219 with_settings :commit_update_keywords => [
220 220 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
221 221 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => ''}
222 222 ] do
223 223
224 224 issue1 = Issue.generate!(:tracker_id => 2)
225 225 issue2 = Issue.generate!
226 226 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
227 227 assert_equal 5, issue1.reload.status_id
228 228 assert_equal 3, issue2.reload.status_id
229 229 end
230 230 end
231 231
232 232 def test_update_keywords_with_multiple_rules_for_the_same_tracker_should_match_keyword
233 233 with_settings :commit_update_keywords => [
234 234 {'keywords' => 'Fixes, Closes', 'status_id' => '5', 'done_ratio' => '100', 'if_tracker_id' => '2'},
235 235 {'keywords' => 'Testing', 'status_id' => '3', 'done_ratio' => '90', 'if_tracker_id' => '2'}
236 236 ] do
237 237
238 238 issue1 = Issue.generate!(:tracker_id => 2)
239 239 issue2 = Issue.generate!(:tracker_id => 2)
240 240 Changeset.generate!(:comments => "Testing ##{issue1.id}, Fixes ##{issue2.id}")
241 241 issue1.reload
242 242 assert_equal 3, issue1.status_id
243 243 assert_equal 90, issue1.done_ratio
244 244 issue2.reload
245 245 assert_equal 5, issue2.status_id
246 246 assert_equal 100, issue2.done_ratio
247 247 end
248 248 end
249 249
250 250 def test_update_keywords_with_multiple_rules_and_no_match
251 251 with_settings :commit_update_keywords => [
252 252 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
253 253 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => '3'}
254 254 ] do
255 255
256 256 issue1 = Issue.generate!(:tracker_id => 2)
257 257 issue2 = Issue.generate!
258 258 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
259 259 assert_equal 5, issue1.reload.status_id
260 260 assert_equal 1, issue2.reload.status_id # no updates
261 261 end
262 262 end
263 263
264 264 def test_commit_referencing_a_subproject_issue
265 265 c = Changeset.new(:repository => Project.find(1).repository,
266 266 :committed_on => Time.now,
267 267 :comments => 'refs #5, a subproject issue',
268 268 :revision => '12345')
269 269 assert c.save
270 270 assert_equal [5], c.issue_ids.sort
271 271 assert c.issues.first.project != c.project
272 272 end
273 273
274 274 def test_commit_closing_a_subproject_issue
275 275 with_settings :commit_update_keywords => [{'keywords' => 'closes', 'status_id' => '5'}],
276 276 :default_language => 'en' do
277 277 issue = Issue.find(5)
278 278 assert !issue.closed?
279 279 assert_difference 'Journal.count' do
280 280 c = Changeset.new(:repository => Project.find(1).repository,
281 281 :committed_on => Time.now,
282 282 :comments => 'closes #5, a subproject issue',
283 283 :revision => '12345')
284 284 assert c.save
285 285 end
286 286 assert issue.reload.closed?
287 287 journal = Journal.order('id DESC').first
288 288 assert_equal issue, journal.issue
289 289 assert_include "Applied in changeset ecookbook:r12345.", journal.notes
290 290 end
291 291 end
292 292
293 293 def test_commit_referencing_a_parent_project_issue
294 294 # repository of child project
295 295 r = Repository::Subversion.create!(
296 296 :project => Project.find(3),
297 297 :url => 'svn://localhost/test')
298 298 c = Changeset.new(:repository => r,
299 299 :committed_on => Time.now,
300 300 :comments => 'refs #2, an issue of a parent project',
301 301 :revision => '12345')
302 302 assert c.save
303 303 assert_equal [2], c.issue_ids.sort
304 304 assert c.issues.first.project != c.project
305 305 end
306 306
307 307 def test_commit_referencing_a_project_with_commit_cross_project_ref_disabled
308 308 r = Repository::Subversion.create!(
309 309 :project => Project.find(3),
310 310 :url => 'svn://localhost/test')
311 311 with_settings :commit_cross_project_ref => '0' do
312 312 c = Changeset.new(:repository => r,
313 313 :committed_on => Time.now,
314 314 :comments => 'refs #4, an issue of a different project',
315 315 :revision => '12345')
316 316 assert c.save
317 317 assert_equal [], c.issue_ids
318 318 end
319 319 end
320 320
321 321 def test_commit_referencing_a_project_with_commit_cross_project_ref_enabled
322 322 r = Repository::Subversion.create!(
323 323 :project => Project.find(3),
324 324 :url => 'svn://localhost/test')
325 325 with_settings :commit_cross_project_ref => '1' do
326 326 c = Changeset.new(:repository => r,
327 327 :committed_on => Time.now,
328 328 :comments => 'refs #4, an issue of a different project',
329 329 :revision => '12345')
330 330 assert c.save
331 331 assert_equal [4], c.issue_ids
332 332 end
333 333 end
334 334
335 335 def test_old_commits_should_not_update_issues_nor_log_time
336 336 Setting.commit_ref_keywords = '*'
337 337 Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}}
338 338 Setting.commit_logtime_enabled = '1'
339 339
340 340 repository = Project.find(1).repository
341 341 repository.created_on = Time.now
342 342 repository.save!
343 343
344 344 c = Changeset.new(:repository => repository,
345 345 :committed_on => 1.month.ago,
346 346 :comments => 'New commit (#2). Fixes #1 @1h',
347 347 :revision => '12345')
348 348 assert_no_difference 'TimeEntry.count' do
349 349 assert c.save
350 350 end
351 351 assert_equal [1, 2], c.issue_ids.sort
352 352 issue = Issue.find(1)
353 353 assert_equal 1, issue.status_id
354 354 assert_equal 0, issue.done_ratio
355 355 end
356 356
357 357 def test_2_repositories_with_same_backend_should_not_link_issue_multiple_times
358 358 Setting.commit_ref_keywords = '*'
359 359 r1 = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///svn1')
360 360 r2 = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn2', :url => 'file:///svn1')
361 361 now = Time.now
362 362 assert_difference 'Issue.find(1).changesets.count' do
363 363 c1 = Changeset.create!(:repository => r1, :committed_on => now, :comments => 'Fixes #1', :revision => '12345')
364 364 c1 = Changeset.create!(:repository => r2, :committed_on => now, :comments => 'Fixes #1', :revision => '12345')
365 365 end
366 366 end
367 367
368 368 def test_text_tag_revision
369 369 c = Changeset.new(:revision => '520')
370 370 assert_equal 'r520', c.text_tag
371 371 end
372 372
373 373 def test_text_tag_revision_with_same_project
374 374 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
375 375 assert_equal 'r520', c.text_tag(Project.find(1))
376 376 end
377 377
378 378 def test_text_tag_revision_with_different_project
379 379 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
380 380 assert_equal 'ecookbook:r520', c.text_tag(Project.find(2))
381 381 end
382 382
383 383 def test_text_tag_revision_with_repository_identifier
384 384 r = Repository::Subversion.create!(
385 385 :project_id => 1,
386 386 :url => 'svn://localhost/test',
387 387 :identifier => 'documents')
388 388 c = Changeset.new(:revision => '520', :repository => r)
389 389 assert_equal 'documents|r520', c.text_tag
390 390 assert_equal 'ecookbook:documents|r520', c.text_tag(Project.find(2))
391 391 end
392 392
393 393 def test_text_tag_hash
394 394 c = Changeset.new(
395 395 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
396 396 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
397 397 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
398 398 end
399 399
400 400 def test_text_tag_hash_with_same_project
401 401 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
402 402 assert_equal 'commit:7234cb27', c.text_tag(Project.find(1))
403 403 end
404 404
405 405 def test_text_tag_hash_with_different_project
406 406 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
407 407 assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2))
408 408 end
409 409
410 410 def test_text_tag_hash_all_number
411 411 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
412 412 assert_equal 'commit:0123456789', c.text_tag
413 413 end
414 414
415 415 def test_text_tag_hash_with_repository_identifier
416 416 r = Repository::Subversion.new(
417 417 :project_id => 1,
418 418 :url => 'svn://localhost/test',
419 419 :identifier => 'documents')
420 420 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => r)
421 421 assert_equal 'commit:documents|7234cb27', c.text_tag
422 422 assert_equal 'ecookbook:commit:documents|7234cb27', c.text_tag(Project.find(2))
423 423 end
424 424
425 425 def test_previous
426 426 changeset = Changeset.find_by_revision('3')
427 427 assert_equal Changeset.find_by_revision('2'), changeset.previous
428 428 end
429 429
430 430 def test_previous_nil
431 431 changeset = Changeset.find_by_revision('1')
432 432 assert_nil changeset.previous
433 433 end
434 434
435 435 def test_next
436 436 changeset = Changeset.find_by_revision('2')
437 437 assert_equal Changeset.find_by_revision('3'), changeset.next
438 438 end
439 439
440 440 def test_next_nil
441 441 changeset = Changeset.find_by_revision('10')
442 442 assert_nil changeset.next
443 443 end
444 444
445 445 def test_comments_should_be_converted_to_utf8
446 446 proj = Project.find(3)
447 447 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
448 448 str = "Texte encod\xe9 en ISO-8859-1.".force_encoding("ASCII-8BIT")
449 449 r = Repository::Bazaar.create!(
450 450 :project => proj,
451 451 :url => '/tmp/test/bazaar',
452 452 :log_encoding => 'ISO-8859-1' )
453 453 assert r
454 454 c = Changeset.new(:repository => r,
455 455 :committed_on => Time.now,
456 456 :revision => '123',
457 457 :scmid => '12345',
458 458 :comments => str)
459 459 assert( c.save )
460 460 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1.".force_encoding("UTF-8")
461 461 assert_equal str_utf8, c.comments
462 462 end
463 463
464 464 def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1
465 465 proj = Project.find(3)
466 466 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
467 467 str1 = "Texte encod\xe9 en ISO-8859-1.".force_encoding("UTF-8")
468 468 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
469 469 r = Repository::Bazaar.create!(
470 470 :project => proj,
471 471 :url => '/tmp/test/bazaar',
472 472 :log_encoding => 'UTF-8' )
473 473 assert r
474 474 c = Changeset.new(:repository => r,
475 475 :committed_on => Time.now,
476 476 :revision => '123',
477 477 :scmid => '12345',
478 478 :comments => str1,
479 479 :committer => str2)
480 480 assert( c.save )
481 481 assert_equal "Texte encod? en ISO-8859-1.", c.comments
482 482 assert_equal "?a?b?c?d?e test", c.committer
483 483 end
484 484
485 485 def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis
486 486 proj = Project.find(3)
487 487 str = "test\xb5\xfetest\xb5\xfe".force_encoding('ASCII-8BIT')
488 488 r = Repository::Bazaar.create!(
489 489 :project => proj,
490 490 :url => '/tmp/test/bazaar',
491 491 :log_encoding => 'ISO-2022-JP' )
492 492 assert r
493 493 c = Changeset.new(:repository => r,
494 494 :committed_on => Time.now,
495 495 :revision => '123',
496 496 :scmid => '12345',
497 497 :comments => str)
498 498 assert( c.save )
499 499 assert_equal "test??test??", c.comments
500 500 end
501 501
502 502 def test_comments_should_be_converted_all_latin1_to_utf8
503 503 s1 = "\xC2\x80"
504 504 s2 = "\xc3\x82\xc2\x80"
505 505 s4 = s2.dup
506 506 s3 = s1.dup
507 507 s1.force_encoding('ASCII-8BIT')
508 508 s2.force_encoding('ASCII-8BIT')
509 509 s3.force_encoding('ISO-8859-1')
510 510 s4.force_encoding('UTF-8')
511 511 assert_equal s3.encode('UTF-8'), s4
512 512 proj = Project.find(3)
513 513 r = Repository::Bazaar.create!(
514 514 :project => proj,
515 515 :url => '/tmp/test/bazaar',
516 516 :log_encoding => 'ISO-8859-1' )
517 517 assert r
518 518 c = Changeset.new(:repository => r,
519 519 :committed_on => Time.now,
520 520 :revision => '123',
521 521 :scmid => '12345',
522 522 :comments => s1)
523 523 assert( c.save )
524 524 assert_equal s4, c.comments
525 525 end
526 526
527 527 def test_invalid_utf8_sequences_in_paths_should_be_replaced
528 528 proj = Project.find(3)
529 529 str1 = "Texte encod\xe9 en ISO-8859-1".force_encoding("UTF-8")
530 530 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
531 531 r = Repository::Bazaar.create!(
532 532 :project => proj,
533 533 :url => '/tmp/test/bazaar',
534 534 :log_encoding => 'UTF-8' )
535 535 assert r
536 536 cs = Changeset.new(
537 537 :repository => r,
538 538 :committed_on => Time.now,
539 539 :revision => '123',
540 540 :scmid => '12345',
541 541 :comments => "test")
542 542 assert(cs.save)
543 543 ch = Change.new(
544 544 :changeset => cs,
545 545 :action => "A",
546 546 :path => str1,
547 547 :from_path => str2,
548 548 :from_revision => "345")
549 549 assert(ch.save)
550 550 assert_equal "Texte encod? en ISO-8859-1", ch.path
551 551 assert_equal "?a?b?c?d?e test", ch.from_path
552 552 end
553 553
554 554 def test_comments_nil
555 555 proj = Project.find(3)
556 556 r = Repository::Bazaar.create!(
557 557 :project => proj,
558 558 :url => '/tmp/test/bazaar',
559 559 :log_encoding => 'ISO-8859-1' )
560 560 assert r
561 561 c = Changeset.new(:repository => r,
562 562 :committed_on => Time.now,
563 563 :revision => '123',
564 564 :scmid => '12345',
565 565 :comments => nil,
566 566 :committer => nil)
567 567 assert( c.save )
568 568 assert_equal "", c.comments
569 assert_equal nil, c.committer
569 assert_nil c.committer
570 570 assert_equal "UTF-8", c.comments.encoding.to_s
571 571 end
572 572
573 573 def test_comments_empty
574 574 proj = Project.find(3)
575 575 r = Repository::Bazaar.create!(
576 576 :project => proj,
577 577 :url => '/tmp/test/bazaar',
578 578 :log_encoding => 'ISO-8859-1' )
579 579 assert r
580 580 c = Changeset.new(:repository => r,
581 581 :committed_on => Time.now,
582 582 :revision => '123',
583 583 :scmid => '12345',
584 584 :comments => "",
585 585 :committer => "")
586 586 assert( c.save )
587 587 assert_equal "", c.comments
588 588 assert_equal "", c.committer
589 589 assert_equal "UTF-8", c.comments.encoding.to_s
590 590 assert_equal "UTF-8", c.committer.encoding.to_s
591 591 end
592 592
593 593 def test_comments_should_accept_more_than_64k
594 594 c = Changeset.new(:repository => Repository.first,
595 595 :committed_on => Time.now,
596 596 :revision => '123',
597 597 :scmid => '12345',
598 598 :comments => "a" * 500.kilobyte)
599 599 assert c.save
600 600 c.reload
601 601 assert_equal 500.kilobyte, c.comments.size
602 602 end
603 603
604 604 def test_identifier
605 605 c = Changeset.find_by_revision('1')
606 606 assert_equal c.revision, c.identifier
607 607 end
608 608 end
@@ -1,335 +1,335
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class CustomFieldTest < ActiveSupport::TestCase
21 21 fixtures :custom_fields, :roles, :projects,
22 22 :trackers, :issue_statuses,
23 23 :issues
24 24
25 25 def test_create
26 26 field = UserCustomField.new(:name => 'Money money money', :field_format => 'float')
27 27 assert field.save
28 28 end
29 29
30 30 def test_before_validation
31 31 field = CustomField.new(:name => 'test_before_validation', :field_format => 'int')
32 32 field.searchable = true
33 33 assert field.save
34 34 assert_equal false, field.searchable
35 35 field.searchable = true
36 36 assert field.save
37 37 assert_equal false, field.searchable
38 38 end
39 39
40 40 def test_regexp_validation
41 41 field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9')
42 42 assert !field.save
43 43 assert_include I18n.t('activerecord.errors.messages.invalid'),
44 44 field.errors[:regexp]
45 45 field.regexp = '[a-z0-9]'
46 46 assert field.save
47 47 end
48 48
49 49 def test_default_value_should_be_validated
50 50 field = CustomField.new(:name => 'Test', :field_format => 'int')
51 51 field.default_value = 'abc'
52 52 assert !field.valid?
53 53 field.default_value = '6'
54 54 assert field.valid?
55 55 end
56 56
57 57 def test_default_value_should_not_be_validated_when_blank
58 58 field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '')
59 59 assert field.valid?
60 60 end
61 61
62 62 def test_field_format_should_be_validated
63 63 field = CustomField.new(:name => 'Test', :field_format => 'foo')
64 64 assert !field.valid?
65 65 end
66 66
67 67 def test_field_format_validation_should_accept_formats_added_at_runtime
68 68 Redmine::FieldFormat.add 'foobar', Class.new(Redmine::FieldFormat::Base)
69 69
70 70 field = CustomField.new(:name => 'Some Custom Field', :field_format => 'foobar')
71 71 assert field.valid?, 'field should be valid'
72 72 ensure
73 73 Redmine::FieldFormat.delete 'foobar'
74 74 end
75 75
76 76 def test_should_not_change_field_format_of_existing_custom_field
77 77 field = CustomField.find(1)
78 78 field.field_format = 'int'
79 79 assert_equal 'list', field.field_format
80 80 end
81 81
82 82 def test_possible_values_should_accept_an_array
83 83 field = CustomField.new
84 84 field.possible_values = ["One value", ""]
85 85 assert_equal ["One value"], field.possible_values
86 86 end
87 87
88 88 def test_possible_values_should_stringify_values
89 89 field = CustomField.new
90 90 field.possible_values = [1, 2]
91 91 assert_equal ["1", "2"], field.possible_values
92 92 end
93 93
94 94 def test_possible_values_should_accept_a_string
95 95 field = CustomField.new
96 96 field.possible_values = "One value"
97 97 assert_equal ["One value"], field.possible_values
98 98 end
99 99
100 100 def test_possible_values_should_return_utf8_encoded_strings
101 101 field = CustomField.new
102 102 s = "Value".force_encoding('BINARY')
103 103 field.possible_values = s
104 104 assert_equal [s], field.possible_values
105 105 assert_equal 'UTF-8', field.possible_values.first.encoding.name
106 106 end
107 107
108 108 def test_possible_values_should_accept_a_multiline_string
109 109 field = CustomField.new
110 110 field.possible_values = "One value\nAnd another one \r\n \n"
111 111 assert_equal ["One value", "And another one"], field.possible_values
112 112 end
113 113
114 114 def test_possible_values_stored_as_binary_should_be_utf8_encoded
115 115 field = CustomField.find(11)
116 116 assert_kind_of Array, field.possible_values
117 117 assert field.possible_values.size > 0
118 118 field.possible_values.each do |value|
119 119 assert_equal "UTF-8", value.encoding.name
120 120 end
121 121 end
122 122
123 123 def test_destroy
124 124 field = CustomField.find(1)
125 125 assert field.destroy
126 126 end
127 127
128 128 def test_new_subclass_instance_should_return_an_instance
129 129 f = CustomField.new_subclass_instance('IssueCustomField')
130 130 assert_kind_of IssueCustomField, f
131 131 end
132 132
133 133 def test_new_subclass_instance_should_set_attributes
134 134 f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test')
135 135 assert_kind_of IssueCustomField, f
136 136 assert_equal 'Test', f.name
137 137 end
138 138
139 139 def test_new_subclass_instance_with_invalid_class_name_should_return_nil
140 140 assert_nil CustomField.new_subclass_instance('WrongClassName')
141 141 end
142 142
143 143 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
144 144 assert_nil CustomField.new_subclass_instance('Project')
145 145 end
146 146
147 147 def test_string_field_validation_with_blank_value
148 148 f = CustomField.new(:field_format => 'string')
149 149
150 150 assert f.valid_field_value?(nil)
151 151 assert f.valid_field_value?('')
152 152
153 153 f.is_required = true
154 154 assert !f.valid_field_value?(nil)
155 155 assert !f.valid_field_value?('')
156 156 end
157 157
158 158 def test_string_field_validation_with_min_and_max_lengths
159 159 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
160 160
161 161 assert f.valid_field_value?(nil)
162 162 assert f.valid_field_value?('')
163 163 assert !f.valid_field_value?(' ')
164 164 assert f.valid_field_value?('a' * 2)
165 165 assert !f.valid_field_value?('a')
166 166 assert !f.valid_field_value?('a' * 6)
167 167 end
168 168
169 169 def test_string_field_validation_with_regexp
170 170 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
171 171
172 172 assert f.valid_field_value?(nil)
173 173 assert f.valid_field_value?('')
174 174 assert !f.valid_field_value?(' ')
175 175 assert f.valid_field_value?('ABC')
176 176 assert !f.valid_field_value?('abc')
177 177 end
178 178
179 179 def test_date_field_validation
180 180 f = CustomField.new(:field_format => 'date')
181 181
182 182 assert f.valid_field_value?(nil)
183 183 assert f.valid_field_value?('')
184 184 assert !f.valid_field_value?(' ')
185 185 assert f.valid_field_value?('1975-07-14')
186 186 assert !f.valid_field_value?('1975-07-33')
187 187 assert !f.valid_field_value?('abc')
188 188 end
189 189
190 190 def test_list_field_validation
191 191 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
192 192
193 193 assert f.valid_field_value?(nil)
194 194 assert f.valid_field_value?('')
195 195 assert !f.valid_field_value?(' ')
196 196 assert f.valid_field_value?('value2')
197 197 assert !f.valid_field_value?('abc')
198 198 end
199 199
200 200 def test_int_field_validation
201 201 f = CustomField.new(:field_format => 'int')
202 202
203 203 assert f.valid_field_value?(nil)
204 204 assert f.valid_field_value?('')
205 205 assert !f.valid_field_value?(' ')
206 206 assert f.valid_field_value?('123')
207 207 assert f.valid_field_value?('+123')
208 208 assert f.valid_field_value?('-123')
209 209 assert !f.valid_field_value?('6abc')
210 210 assert f.valid_field_value?(123)
211 211 end
212 212
213 213 def test_float_field_validation
214 214 f = CustomField.new(:field_format => 'float')
215 215
216 216 assert f.valid_field_value?(nil)
217 217 assert f.valid_field_value?('')
218 218 assert !f.valid_field_value?(' ')
219 219 assert f.valid_field_value?('11.2')
220 220 assert f.valid_field_value?('-6.250')
221 221 assert f.valid_field_value?('5')
222 222 assert !f.valid_field_value?('6abc')
223 223 assert f.valid_field_value?(11.2)
224 224 end
225 225
226 226 def test_multi_field_validation
227 227 f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2'])
228 228
229 229 assert f.valid_field_value?(nil)
230 230 assert f.valid_field_value?('')
231 231 assert !f.valid_field_value?(' ')
232 232 assert f.valid_field_value?([])
233 233 assert f.valid_field_value?([nil])
234 234 assert f.valid_field_value?([''])
235 235 assert !f.valid_field_value?([' '])
236 236
237 237 assert f.valid_field_value?('value2')
238 238 assert !f.valid_field_value?('abc')
239 239
240 240 assert f.valid_field_value?(['value2'])
241 241 assert !f.valid_field_value?(['abc'])
242 242
243 243 assert f.valid_field_value?(['', 'value2'])
244 244 assert !f.valid_field_value?(['', 'abc'])
245 245
246 246 assert f.valid_field_value?(['value1', 'value2'])
247 247 assert !f.valid_field_value?(['value1', 'abc'])
248 248 end
249 249
250 250 def test_changing_multiple_to_false_should_delete_multiple_values
251 251 field = ProjectCustomField.create!(:name => 'field', :field_format => 'list', :multiple => 'true', :possible_values => ['field1', 'field2'])
252 252 other = ProjectCustomField.create!(:name => 'other', :field_format => 'list', :multiple => 'true', :possible_values => ['other1', 'other2'])
253 253
254 254 item_with_multiple_values = Project.generate!(:custom_field_values => {field.id => ['field1', 'field2'], other.id => ['other1', 'other2']})
255 255 item_with_single_values = Project.generate!(:custom_field_values => {field.id => ['field1'], other.id => ['other2']})
256 256
257 257 assert_difference 'CustomValue.count', -1 do
258 258 field.multiple = false
259 259 field.save!
260 260 end
261 261
262 262 item_with_multiple_values = Project.find(item_with_multiple_values.id)
263 263 assert_kind_of String, item_with_multiple_values.custom_field_value(field)
264 264 assert_kind_of Array, item_with_multiple_values.custom_field_value(other)
265 265 assert_equal 2, item_with_multiple_values.custom_field_value(other).size
266 266 end
267 267
268 268 def test_value_class_should_return_the_class_used_for_fields_values
269 269 assert_equal User, CustomField.new(:field_format => 'user').value_class
270 270 assert_equal Version, CustomField.new(:field_format => 'version').value_class
271 271 end
272 272
273 273 def test_value_class_should_return_nil_for_other_fields
274 274 assert_nil CustomField.new(:field_format => 'text').value_class
275 275 assert_nil CustomField.new.value_class
276 276 end
277 277
278 278 def test_value_from_keyword_for_list_custom_field
279 279 field = CustomField.find(1)
280 280 assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1))
281 281 end
282 282
283 283 def test_visibile_scope_with_admin_should_return_all_custom_fields
284 284 admin = User.generate! {|user| user.admin = true}
285 285 CustomField.delete_all
286 286 fields = [
287 287 CustomField.generate!(:visible => true),
288 288 CustomField.generate!(:visible => false),
289 289 CustomField.generate!(:visible => false, :role_ids => [1, 3]),
290 290 CustomField.generate!(:visible => false, :role_ids => [1, 2]),
291 291 ]
292 292
293 293 assert_equal 4, CustomField.visible(admin).count
294 294 end
295 295
296 296 def test_visibile_scope_with_non_admin_user_should_return_visible_custom_fields
297 297 CustomField.delete_all
298 298 fields = [
299 299 CustomField.generate!(:visible => true),
300 300 CustomField.generate!(:visible => false),
301 301 CustomField.generate!(:visible => false, :role_ids => [1, 3]),
302 302 CustomField.generate!(:visible => false, :role_ids => [1, 2]),
303 303 ]
304 304 user = User.generate!
305 305 User.add_to_project(user, Project.first, Role.find(3))
306 306
307 307 assert_equal [fields[0], fields[2]], CustomField.visible(user).order("id").to_a
308 308 end
309 309
310 310 def test_visibile_scope_with_anonymous_user_should_return_visible_custom_fields
311 311 CustomField.delete_all
312 312 fields = [
313 313 CustomField.generate!(:visible => true),
314 314 CustomField.generate!(:visible => false),
315 315 CustomField.generate!(:visible => false, :role_ids => [1, 3]),
316 316 CustomField.generate!(:visible => false, :role_ids => [1, 2]),
317 317 ]
318 318
319 319 assert_equal [fields[0]], CustomField.visible(User.anonymous).order("id").to_a
320 320 end
321 321
322 322 def test_float_cast_blank_value_should_return_nil
323 323 field = CustomField.new(:field_format => 'float')
324 assert_equal nil, field.cast_value(nil)
325 assert_equal nil, field.cast_value('')
324 assert_nil field.cast_value(nil)
325 assert_nil field.cast_value('')
326 326 end
327 327
328 328 def test_float_cast_valid_value_should_return_float
329 329 field = CustomField.new(:field_format => 'float')
330 330 assert_equal 12.0, field.cast_value('12')
331 331 assert_equal 12.5, field.cast_value('12.5')
332 332 assert_equal 12.5, field.cast_value('+12.5')
333 333 assert_equal -12.5, field.cast_value('-12.5')
334 334 end
335 335 end
@@ -1,78 +1,78
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class CustomFieldUserFormatTest < ActiveSupport::TestCase
21 21 fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues
22 22
23 23 def setup
24 24 @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user')
25 25 end
26 26
27 27 def test_possible_values_options_with_no_arguments
28 28 assert_equal [], @field.possible_values_options
29 29 assert_equal [], @field.possible_values_options(nil)
30 30 end
31 31
32 32 def test_possible_values_options_with_project_resource
33 33 project = Project.find(1)
34 34 possible_values_options = @field.possible_values_options(project.issues.first)
35 35 assert possible_values_options.any?
36 36 assert_equal project.users.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options
37 37 end
38 38
39 39 def test_possible_values_options_with_array
40 40 projects = Project.find([1, 2])
41 41 possible_values_options = @field.possible_values_options(projects)
42 42 assert possible_values_options.any?
43 43 assert_equal (projects.first.users & projects.last.users).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options
44 44 end
45 45
46 46 def test_possible_custom_value_options_should_not_include_locked_users
47 47 custom_value = CustomValue.new(:customized => Issue.find(1), :custom_field => @field)
48 48 assert_include '2', @field.possible_custom_value_options(custom_value).map(&:last)
49 49
50 50 assert User.find(2).lock!
51 51 assert_not_include '2', @field.possible_custom_value_options(custom_value).map(&:last)
52 52 end
53 53
54 54 def test_possible_custom_value_options_should_include_user_that_was_assigned_to_the_custom_value
55 55 user = User.generate!
56 56 custom_value = CustomValue.new(:customized => Issue.find(1), :custom_field => @field)
57 57 assert_not_include user.id.to_s, @field.possible_custom_value_options(custom_value).map(&:last)
58 58
59 59 custom_value.value = user.id
60 60 custom_value.save!
61 61 assert_include user.id.to_s, @field.possible_custom_value_options(custom_value).map(&:last)
62 62 end
63 63
64 64 def test_cast_blank_value
65 assert_equal nil, @field.cast_value(nil)
66 assert_equal nil, @field.cast_value("")
65 assert_nil @field.cast_value(nil)
66 assert_nil @field.cast_value("")
67 67 end
68 68
69 69 def test_cast_valid_value
70 70 user = @field.cast_value("2")
71 71 assert_kind_of User, user
72 72 assert_equal User.find(2), user
73 73 end
74 74
75 75 def test_cast_invalid_value
76 assert_equal nil, @field.cast_value("187")
76 assert_nil @field.cast_value("187")
77 77 end
78 78 end
@@ -1,61 +1,61
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class CustomFieldVersionFormatTest < ActiveSupport::TestCase
21 21 fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues, :versions
22 22
23 23 def setup
24 24 @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'version')
25 25 end
26 26
27 27 def test_possible_values_options_with_no_arguments
28 28 Version.delete_all
29 29 assert_equal [], @field.possible_values_options
30 30 assert_equal [], @field.possible_values_options(nil)
31 31 end
32 32
33 33 def test_possible_values_options_with_project_resource
34 34 project = Project.find(1)
35 35 possible_values_options = @field.possible_values_options(project.issues.first)
36 36 assert possible_values_options.any?
37 37 assert_equal project.shared_versions.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options
38 38 end
39 39
40 40 def test_possible_values_options_with_array
41 41 projects = Project.find([1, 2])
42 42 possible_values_options = @field.possible_values_options(projects)
43 43 assert possible_values_options.any?
44 44 assert_equal (projects.first.shared_versions & projects.last.shared_versions).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options
45 45 end
46 46
47 47 def test_cast_blank_value
48 assert_equal nil, @field.cast_value(nil)
49 assert_equal nil, @field.cast_value("")
48 assert_nil @field.cast_value(nil)
49 assert_nil @field.cast_value("")
50 50 end
51 51
52 52 def test_cast_valid_value
53 53 version = @field.cast_value("2")
54 54 assert_kind_of Version, version
55 55 assert_equal Version.find(2), version
56 56 end
57 57
58 58 def test_cast_invalid_value
59 assert_equal nil, @field.cast_value("187")
59 assert_nil @field.cast_value("187")
60 60 end
61 61 end
@@ -1,169 +1,169
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class GroupTest < ActiveSupport::TestCase
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :enumerations, :users,
23 23 :projects_trackers,
24 24 :roles,
25 25 :member_roles,
26 26 :members,
27 27 :groups_users
28 28
29 29 include Redmine::I18n
30 30
31 31 def test_create
32 32 g = Group.new(:name => 'New group')
33 33 assert g.save
34 34 g.reload
35 35 assert_equal 'New group', g.name
36 36 end
37 37
38 38 def test_name_should_accept_255_characters
39 39 name = 'a' * 255
40 40 g = Group.new(:name => name)
41 41 assert g.save
42 42 g.reload
43 43 assert_equal name, g.name
44 44 end
45 45
46 46 def test_blank_name_error_message
47 47 set_language_if_valid 'en'
48 48 g = Group.new
49 49 assert !g.save
50 50 assert_include "Name cannot be blank", g.errors.full_messages
51 51 end
52 52
53 53 def test_blank_name_error_message_fr
54 54 set_language_if_valid 'fr'
55 55 str = "Nom doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
56 56 g = Group.new
57 57 assert !g.save
58 58 assert_include str, g.errors.full_messages
59 59 end
60 60
61 61 def test_group_roles_should_be_given_to_added_user
62 62 group = Group.find(11)
63 63 user = User.find(9)
64 64 project = Project.first
65 65
66 66 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
67 67 group.users << user
68 68 assert user.member_of?(project)
69 69 end
70 70
71 71 def test_new_roles_should_be_given_to_existing_user
72 72 group = Group.find(11)
73 73 user = User.find(9)
74 74 project = Project.first
75 75
76 76 group.users << user
77 77 m = Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
78 78 assert user.member_of?(project)
79 79 end
80 80
81 81 def test_user_roles_should_updated_when_updating_user_ids
82 82 group = Group.find(11)
83 83 user = User.find(9)
84 84 project = Project.first
85 85
86 86 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
87 87 group.user_ids = [user.id]
88 88 group.save!
89 89 assert User.find(9).member_of?(project)
90 90
91 91 group.user_ids = [1]
92 92 group.save!
93 93 assert !User.find(9).member_of?(project)
94 94 end
95 95
96 96 def test_user_roles_should_updated_when_updating_group_roles
97 97 group = Group.find(11)
98 98 user = User.find(9)
99 99 project = Project.first
100 100 group.users << user
101 101 m = Member.create!(:principal => group, :project => project, :role_ids => [1])
102 102 assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort
103 103
104 104 m.role_ids = [1, 2]
105 105 assert_equal [1, 2], user.reload.roles_for_project(project).collect(&:id).sort
106 106
107 107 m.role_ids = [2]
108 108 assert_equal [2], user.reload.roles_for_project(project).collect(&:id).sort
109 109
110 110 m.role_ids = [1]
111 111 assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort
112 112 end
113 113
114 114 def test_user_memberships_should_be_removed_when_removing_group_membership
115 115 assert User.find(8).member_of?(Project.find(5))
116 116 Member.find_by_project_id_and_user_id(5, 10).destroy
117 117 assert !User.find(8).member_of?(Project.find(5))
118 118 end
119 119
120 120 def test_user_roles_should_be_removed_when_removing_user_from_group
121 121 assert User.find(8).member_of?(Project.find(5))
122 122 User.find(8).groups = []
123 123 assert !User.find(8).member_of?(Project.find(5))
124 124 end
125 125
126 126 def test_destroy_should_unassign_issues
127 127 group = Group.find(10)
128 128 Issue.where(:id => 1).update_all(["assigned_to_id = ?", group.id])
129 129
130 130 assert group.destroy
131 131 assert group.destroyed?
132 132
133 assert_equal nil, Issue.find(1).assigned_to_id
133 assert_nil Issue.find(1).assigned_to_id
134 134 end
135 135
136 136 def test_builtin_groups_should_be_created_if_missing
137 137 Group.delete_all
138 138
139 139 assert_difference 'Group.count', 2 do
140 140 group = Group.anonymous
141 141 assert_equal GroupAnonymous, group.class
142 142
143 143 group = Group.non_member
144 144 assert_equal GroupNonMember, group.class
145 145 end
146 146 end
147 147
148 148 def test_builtin_in_group_should_be_uniq
149 149 group = GroupAnonymous.new
150 150 group.name = 'Foo'
151 151 assert !group.save
152 152 end
153 153
154 154 def test_builtin_in_group_should_not_accept_users
155 155 group = Group.anonymous
156 156 assert_raise RuntimeError do
157 157 group.users << User.find(1)
158 158 end
159 159 assert_equal 0, group.reload.users.count
160 160 end
161 161
162 162 def test_sorted_scope_should_sort_groups_alphabetically
163 163 Group.delete_all
164 164 b = Group.generate!(:name => 'B')
165 165 a = Group.generate!(:name => 'A')
166 166
167 167 assert_equal %w(A B), Group.sorted.to_a.map(&:name)
168 168 end
169 169 end
@@ -1,1576 +1,1576
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../../test_helper', __FILE__)
21 21
22 22 class ApplicationHelperTest < Redmine::HelperTest
23 23 include Redmine::I18n
24 24 include ERB::Util
25 25 include Rails.application.routes.url_helpers
26 26
27 27 fixtures :projects, :enabled_modules,
28 28 :users, :email_addresses,
29 29 :members, :member_roles, :roles,
30 30 :repositories, :changesets,
31 31 :projects_trackers,
32 32 :trackers, :issue_statuses, :issues, :versions, :documents,
33 33 :wikis, :wiki_pages, :wiki_contents,
34 34 :boards, :messages, :news,
35 35 :attachments, :enumerations
36 36
37 37 def setup
38 38 super
39 39 set_tmp_attachments_directory
40 40 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82".force_encoding('UTF-8')
41 41 end
42 42
43 43 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
44 44 User.current = User.find_by_login('admin')
45 45
46 46 @project = Issue.first.project # Used by helper
47 47 response = link_to_if_authorized('By controller/actionr',
48 48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 49 assert_match /href/, response
50 50 end
51 51
52 52 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
53 53 User.current = User.find_by_login('dlopper')
54 54 @project = Project.find('private-child')
55 55 issue = @project.issues.first
56 56 assert !issue.visible?
57 57
58 58 response = link_to_if_authorized('Never displayed',
59 59 {:controller => 'issues', :action => 'show', :id => issue})
60 60 assert_nil response
61 61 end
62 62
63 63 def test_auto_links
64 64 to_test = {
65 65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
66 66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
67 67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
68 68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
69 69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
70 70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
71 71 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
72 72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
73 73 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
74 74 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
75 75 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
76 76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
77 77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
78 78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
79 79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
80 80 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
81 81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
82 82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
83 83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
84 84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
85 85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
86 86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
87 87 # two exclamation marks
88 88 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
89 89 # escaping
90 90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
91 91 # wrap in angle brackets
92 92 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
93 93 # invalid urls
94 94 'http://' => 'http://',
95 95 'www.' => 'www.',
96 96 'test-www.bar.com' => 'test-www.bar.com',
97 97 }
98 98 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
99 99 end
100 100
101 101 def test_auto_links_with_non_ascii_characters
102 102 to_test = {
103 103 "http://foo.bar/#{@russian_test}" =>
104 104 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
105 105 }
106 106 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
107 107 end
108 108
109 109 def test_auto_mailto
110 110 to_test = {
111 111 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
112 112 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
113 113 }
114 114 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
115 115 end
116 116
117 117 def test_inline_images
118 118 to_test = {
119 119 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
120 120 'floating !>http://foo.bar/image.jpg!' => 'floating <span style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></span>',
121 121 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
122 122 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
123 123 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
124 124 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
125 125 }
126 126 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
127 127 end
128 128
129 129 def test_inline_images_inside_tags
130 130 raw = <<-RAW
131 131 h1. !foo.png! Heading
132 132
133 133 Centered image:
134 134
135 135 p=. !bar.gif!
136 136 RAW
137 137
138 138 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
139 139 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
140 140 end
141 141
142 142 def test_attached_images
143 143 to_test = {
144 144 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
145 145 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
146 146 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
147 147 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
148 148 # link image
149 149 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
150 150 }
151 151 attachments = Attachment.all
152 152 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
153 153 end
154 154
155 155 def test_attached_images_with_textile_and_non_ascii_filename
156 156 attachment = Attachment.generate!(:filename => 'cafΓ©.jpg')
157 157 with_settings :text_formatting => 'textile' do
158 158 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
159 159 textilizable("!cafΓ©.jpg!)", :attachments => [attachment])
160 160 end
161 161 end
162 162
163 163 def test_attached_images_with_markdown_and_non_ascii_filename
164 164 skip unless Object.const_defined?(:Redcarpet)
165 165
166 166 attachment = Attachment.generate!(:filename => 'cafΓ©.jpg')
167 167 with_settings :text_formatting => 'markdown' do
168 168 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
169 169 textilizable("![](cafΓ©.jpg)", :attachments => [attachment])
170 170 end
171 171 end
172 172
173 173 def test_attached_images_filename_extension
174 174 set_tmp_attachments_directory
175 175 a1 = Attachment.new(
176 176 :container => Issue.find(1),
177 177 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
178 178 :author => User.find(1))
179 179 assert a1.save
180 180 assert_equal "testtest.JPG", a1.filename
181 181 assert_equal "image/jpeg", a1.content_type
182 182 assert a1.image?
183 183
184 184 a2 = Attachment.new(
185 185 :container => Issue.find(1),
186 186 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
187 187 :author => User.find(1))
188 188 assert a2.save
189 189 assert_equal "testtest.jpeg", a2.filename
190 190 assert_equal "image/jpeg", a2.content_type
191 191 assert a2.image?
192 192
193 193 a3 = Attachment.new(
194 194 :container => Issue.find(1),
195 195 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
196 196 :author => User.find(1))
197 197 assert a3.save
198 198 assert_equal "testtest.JPE", a3.filename
199 199 assert_equal "image/jpeg", a3.content_type
200 200 assert a3.image?
201 201
202 202 a4 = Attachment.new(
203 203 :container => Issue.find(1),
204 204 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
205 205 :author => User.find(1))
206 206 assert a4.save
207 207 assert_equal "Testtest.BMP", a4.filename
208 208 assert_equal "image/x-ms-bmp", a4.content_type
209 209 assert a4.image?
210 210
211 211 to_test = {
212 212 'Inline image: !testtest.jpg!' =>
213 213 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
214 214 'Inline image: !testtest.jpeg!' =>
215 215 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
216 216 'Inline image: !testtest.jpe!' =>
217 217 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
218 218 'Inline image: !testtest.bmp!' =>
219 219 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
220 220 }
221 221
222 222 attachments = [a1, a2, a3, a4]
223 223 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
224 224 end
225 225
226 226 def test_attached_images_should_read_later
227 227 set_fixtures_attachments_directory
228 228 a1 = Attachment.find(16)
229 229 assert_equal "testfile.png", a1.filename
230 230 assert a1.readable?
231 231 assert (! a1.visible?(User.anonymous))
232 232 assert a1.visible?(User.find(2))
233 233 a2 = Attachment.find(17)
234 234 assert_equal "testfile.PNG", a2.filename
235 235 assert a2.readable?
236 236 assert (! a2.visible?(User.anonymous))
237 237 assert a2.visible?(User.find(2))
238 238 assert a1.created_on < a2.created_on
239 239
240 240 to_test = {
241 241 'Inline image: !testfile.png!' =>
242 242 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
243 243 'Inline image: !Testfile.PNG!' =>
244 244 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
245 245 }
246 246 attachments = [a1, a2]
247 247 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
248 248 set_tmp_attachments_directory
249 249 end
250 250
251 251 def test_textile_external_links
252 252 to_test = {
253 253 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
254 254 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
255 255 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
256 256 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
257 257 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
258 258 # no multiline link text
259 259 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
260 260 # mailto link
261 261 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
262 262 # two exclamation marks
263 263 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
264 264 # escaping
265 265 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
266 266 }
267 267 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
268 268 end
269 269
270 270 def test_textile_external_links_with_non_ascii_characters
271 271 to_test = {
272 272 %|This is a "link":http://foo.bar/#{@russian_test}| =>
273 273 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
274 274 }
275 275 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
276 276 end
277 277
278 278 def test_redmine_links
279 279 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
280 280 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
281 281 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
282 282 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
283 283 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
284 284 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
285 285
286 286 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
287 287 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
288 288 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
289 289 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
290 290
291 291 changeset_link2 = link_to('691322a8eb01e11fd7',
292 292 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
293 293 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
294 294
295 295 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
296 296 :class => 'document')
297 297
298 298 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
299 299 :class => 'version')
300 300
301 301 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
302 302
303 303 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
304 304
305 305 news_url = {:controller => 'news', :action => 'show', :id => 1}
306 306
307 307 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
308 308
309 309 source_url = '/projects/ecookbook/repository/entry/some/file'
310 310 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
311 311 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
312 312 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
313 313 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
314 314
315 315 export_url = '/projects/ecookbook/repository/raw/some/file'
316 316 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
317 317 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
318 318 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
319 319 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
320 320
321 321 to_test = {
322 322 # tickets
323 323 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
324 324 # ticket notes
325 325 '#3-14' => note_link,
326 326 '#3#note-14' => note_link2,
327 327 # should not ignore leading zero
328 328 '#03' => '#03',
329 329 # changesets
330 330 'r1' => revision_link,
331 331 'r1.' => "#{revision_link}.",
332 332 'r1, r2' => "#{revision_link}, #{revision_link2}",
333 333 'r1,r2' => "#{revision_link},#{revision_link2}",
334 334 'commit:691322a8eb01e11fd7' => changeset_link2,
335 335 # documents
336 336 'document#1' => document_link,
337 337 'document:"Test document"' => document_link,
338 338 # versions
339 339 'version#2' => version_link,
340 340 'version:1.0' => version_link,
341 341 'version:"1.0"' => version_link,
342 342 # source
343 343 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
344 344 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
345 345 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
346 346 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
347 347 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
348 348 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
349 349 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
350 350 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
351 351 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
352 352 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
353 353 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
354 354 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
355 355 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
356 356 # export
357 357 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
358 358 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
359 359 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
360 360 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
361 361 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
362 362 # forum
363 363 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
364 364 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
365 365 # message
366 366 'message#4' => link_to('Post 2', message_url, :class => 'message'),
367 367 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
368 368 # news
369 369 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
370 370 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
371 371 # project
372 372 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
373 373 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
374 374 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
375 375 # not found
376 376 '#0123456789' => '#0123456789',
377 377 # invalid expressions
378 378 'source:' => 'source:',
379 379 # url hash
380 380 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
381 381 }
382 382 @project = Project.find(1)
383 383 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
384 384 end
385 385
386 386 def test_should_not_parse_redmine_links_inside_link
387 387 raw = "r1 should not be parsed in http://example.com/url-r1/"
388 388 assert_match %r{<p><a class="changeset".*>r1</a> should not be parsed in <a class="external" href="http://example.com/url-r1/">http://example.com/url-r1/</a></p>},
389 389 textilizable(raw, :project => Project.find(1))
390 390 end
391 391
392 392 def test_redmine_links_with_a_different_project_before_current_project
393 393 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
394 394 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
395 395 @project = Project.find(3)
396 396 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
397 397 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
398 398 assert_equal "<p>#{result1} #{result2}</p>",
399 399 textilizable("ecookbook:version:1.4.4 version:1.4.4")
400 400 end
401 401
402 402 def test_escaped_redmine_links_should_not_be_parsed
403 403 to_test = [
404 404 '#3.',
405 405 '#3-14.',
406 406 '#3#-note14.',
407 407 'r1',
408 408 'document#1',
409 409 'document:"Test document"',
410 410 'version#2',
411 411 'version:1.0',
412 412 'version:"1.0"',
413 413 'source:/some/file'
414 414 ]
415 415 @project = Project.find(1)
416 416 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
417 417 end
418 418
419 419 def test_cross_project_redmine_links
420 420 source_link = link_to('ecookbook:source:/some/file',
421 421 {:controller => 'repositories', :action => 'entry',
422 422 :id => 'ecookbook', :path => ['some', 'file']},
423 423 :class => 'source')
424 424 changeset_link = link_to('ecookbook:r2',
425 425 {:controller => 'repositories', :action => 'revision',
426 426 :id => 'ecookbook', :rev => 2},
427 427 :class => 'changeset',
428 428 :title => 'This commit fixes #1, #2 and references #1 & #3')
429 429 to_test = {
430 430 # documents
431 431 'document:"Test document"' => 'document:"Test document"',
432 432 'ecookbook:document:"Test document"' =>
433 433 link_to("Test document", "/documents/1", :class => "document"),
434 434 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
435 435 # versions
436 436 'version:"1.0"' => 'version:"1.0"',
437 437 'ecookbook:version:"1.0"' =>
438 438 link_to("1.0", "/versions/2", :class => "version"),
439 439 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
440 440 # changeset
441 441 'r2' => 'r2',
442 442 'ecookbook:r2' => changeset_link,
443 443 'invalid:r2' => 'invalid:r2',
444 444 # source
445 445 'source:/some/file' => 'source:/some/file',
446 446 'ecookbook:source:/some/file' => source_link,
447 447 'invalid:source:/some/file' => 'invalid:source:/some/file',
448 448 }
449 449 @project = Project.find(3)
450 450 to_test.each do |text, result|
451 451 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
452 452 end
453 453 end
454 454
455 455 def test_redmine_links_by_name_should_work_with_html_escaped_characters
456 456 v = Version.generate!(:name => "Test & Show.txt", :project_id => 1)
457 457 link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version")
458 458
459 459 @project = v.project
460 460 assert_equal "<p>#{link}</p>", textilizable('version:"Test & Show.txt"')
461 461 end
462 462
463 463 def test_link_to_issue_subject
464 464 issue = Issue.generate!(:subject => "01234567890123456789")
465 465 str = link_to_issue(issue, :truncate => 10)
466 466 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
467 467 assert_equal "#{result}: 0123456...", str
468 468
469 469 issue = Issue.generate!(:subject => "<&>")
470 470 str = link_to_issue(issue)
471 471 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
472 472 assert_equal "#{result}: &lt;&amp;&gt;", str
473 473
474 474 issue = Issue.generate!(:subject => "<&>0123456789012345")
475 475 str = link_to_issue(issue, :truncate => 10)
476 476 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
477 477 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
478 478 end
479 479
480 480 def test_link_to_issue_title
481 481 long_str = "0123456789" * 5
482 482
483 483 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
484 484 str = link_to_issue(issue, :subject => false)
485 485 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
486 486 :class => issue.css_classes,
487 487 :title => "#{long_str}0123456...")
488 488 assert_equal result, str
489 489
490 490 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
491 491 str = link_to_issue(issue, :subject => false)
492 492 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
493 493 :class => issue.css_classes,
494 494 :title => "<&>#{long_str}0123...")
495 495 assert_equal result, str
496 496 end
497 497
498 498 def test_multiple_repositories_redmine_links
499 499 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
500 500 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
501 501 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
502 502 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
503 503
504 504 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
505 505 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
506 506 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
507 507 :class => 'changeset', :title => '')
508 508 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
509 509 :class => 'changeset', :title => '')
510 510
511 511 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
512 512 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
513 513
514 514 to_test = {
515 515 'r2' => changeset_link,
516 516 'svn_repo-1|r123' => svn_changeset_link,
517 517 'invalid|r123' => 'invalid|r123',
518 518 'commit:hg1|abcd' => hg_changeset_link,
519 519 'commit:invalid|abcd' => 'commit:invalid|abcd',
520 520 # source
521 521 'source:some/file' => source_link,
522 522 'source:hg1|some/file' => hg_source_link,
523 523 'source:invalid|some/file' => 'source:invalid|some/file',
524 524 }
525 525
526 526 @project = Project.find(1)
527 527 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
528 528 end
529 529
530 530 def test_cross_project_multiple_repositories_redmine_links
531 531 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
532 532 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
533 533 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
534 534 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
535 535
536 536 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
537 537 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
538 538 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
539 539 :class => 'changeset', :title => '')
540 540 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
541 541 :class => 'changeset', :title => '')
542 542
543 543 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
544 544 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
545 545
546 546 to_test = {
547 547 'ecookbook:r2' => changeset_link,
548 548 'ecookbook:svn1|r123' => svn_changeset_link,
549 549 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
550 550 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
551 551 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
552 552 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
553 553 # source
554 554 'ecookbook:source:some/file' => source_link,
555 555 'ecookbook:source:hg1|some/file' => hg_source_link,
556 556 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
557 557 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
558 558 }
559 559
560 560 @project = Project.find(3)
561 561 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
562 562 end
563 563
564 564 def test_redmine_links_git_commit
565 565 changeset_link = link_to('abcd',
566 566 {
567 567 :controller => 'repositories',
568 568 :action => 'revision',
569 569 :id => 'subproject1',
570 570 :rev => 'abcd',
571 571 },
572 572 :class => 'changeset', :title => 'test commit')
573 573 to_test = {
574 574 'commit:abcd' => changeset_link,
575 575 }
576 576 @project = Project.find(3)
577 577 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
578 578 assert r
579 579 c = Changeset.new(:repository => r,
580 580 :committed_on => Time.now,
581 581 :revision => 'abcd',
582 582 :scmid => 'abcd',
583 583 :comments => 'test commit')
584 584 assert( c.save )
585 585 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
586 586 end
587 587
588 588 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
589 589 def test_redmine_links_darcs_commit
590 590 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
591 591 {
592 592 :controller => 'repositories',
593 593 :action => 'revision',
594 594 :id => 'subproject1',
595 595 :rev => '123',
596 596 },
597 597 :class => 'changeset', :title => 'test commit')
598 598 to_test = {
599 599 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
600 600 }
601 601 @project = Project.find(3)
602 602 r = Repository::Darcs.create!(
603 603 :project => @project, :url => '/tmp/test/darcs',
604 604 :log_encoding => 'UTF-8')
605 605 assert r
606 606 c = Changeset.new(:repository => r,
607 607 :committed_on => Time.now,
608 608 :revision => '123',
609 609 :scmid => '20080308225258-98289-abcd456efg.gz',
610 610 :comments => 'test commit')
611 611 assert( c.save )
612 612 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
613 613 end
614 614
615 615 def test_redmine_links_mercurial_commit
616 616 changeset_link_rev = link_to('r123',
617 617 {
618 618 :controller => 'repositories',
619 619 :action => 'revision',
620 620 :id => 'subproject1',
621 621 :rev => '123' ,
622 622 },
623 623 :class => 'changeset', :title => 'test commit')
624 624 changeset_link_commit = link_to('abcd',
625 625 {
626 626 :controller => 'repositories',
627 627 :action => 'revision',
628 628 :id => 'subproject1',
629 629 :rev => 'abcd' ,
630 630 },
631 631 :class => 'changeset', :title => 'test commit')
632 632 to_test = {
633 633 'r123' => changeset_link_rev,
634 634 'commit:abcd' => changeset_link_commit,
635 635 }
636 636 @project = Project.find(3)
637 637 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
638 638 assert r
639 639 c = Changeset.new(:repository => r,
640 640 :committed_on => Time.now,
641 641 :revision => '123',
642 642 :scmid => 'abcd',
643 643 :comments => 'test commit')
644 644 assert( c.save )
645 645 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
646 646 end
647 647
648 648 def test_attachment_links
649 649 text = 'attachment:error281.txt'
650 650 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
651 651 :class => "attachment")
652 652 assert_equal "<p>#{result}</p>",
653 653 textilizable(text,
654 654 :attachments => Issue.find(3).attachments),
655 655 "#{text} failed"
656 656 end
657 657
658 658 def test_attachment_link_should_link_to_latest_attachment
659 659 set_tmp_attachments_directory
660 660 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
661 661 a2 = Attachment.generate!(:filename => "test.txt")
662 662 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
663 663 :class => "attachment")
664 664 assert_equal "<p>#{result}</p>",
665 665 textilizable('attachment:test.txt', :attachments => [a1, a2])
666 666 end
667 667
668 668 def test_wiki_links
669 669 russian_eacape = CGI.escape(@russian_test)
670 670 to_test = {
671 671 '[[CookBook documentation]]' =>
672 672 link_to("CookBook documentation",
673 673 "/projects/ecookbook/wiki/CookBook_documentation",
674 674 :class => "wiki-page"),
675 675 '[[Another page|Page]]' =>
676 676 link_to("Page",
677 677 "/projects/ecookbook/wiki/Another_page",
678 678 :class => "wiki-page"),
679 679 # title content should be formatted
680 680 '[[Another page|With _styled_ *title*]]' =>
681 681 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
682 682 "/projects/ecookbook/wiki/Another_page",
683 683 :class => "wiki-page"),
684 684 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
685 685 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
686 686 "/projects/ecookbook/wiki/Another_page",
687 687 :class => "wiki-page"),
688 688 # link with anchor
689 689 '[[CookBook documentation#One-section]]' =>
690 690 link_to("CookBook documentation",
691 691 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
692 692 :class => "wiki-page"),
693 693 '[[Another page#anchor|Page]]' =>
694 694 link_to("Page",
695 695 "/projects/ecookbook/wiki/Another_page#anchor",
696 696 :class => "wiki-page"),
697 697 # UTF8 anchor
698 698 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
699 699 link_to(@russian_test,
700 700 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
701 701 :class => "wiki-page"),
702 702 # page that doesn't exist
703 703 '[[Unknown page]]' =>
704 704 link_to("Unknown page",
705 705 "/projects/ecookbook/wiki/Unknown_page",
706 706 :class => "wiki-page new"),
707 707 '[[Unknown page|404]]' =>
708 708 link_to("404",
709 709 "/projects/ecookbook/wiki/Unknown_page",
710 710 :class => "wiki-page new"),
711 711 # link to another project wiki
712 712 '[[onlinestore:]]' =>
713 713 link_to("onlinestore",
714 714 "/projects/onlinestore/wiki",
715 715 :class => "wiki-page"),
716 716 '[[onlinestore:|Wiki]]' =>
717 717 link_to("Wiki",
718 718 "/projects/onlinestore/wiki",
719 719 :class => "wiki-page"),
720 720 '[[onlinestore:Start page]]' =>
721 721 link_to("Start page",
722 722 "/projects/onlinestore/wiki/Start_page",
723 723 :class => "wiki-page"),
724 724 '[[onlinestore:Start page|Text]]' =>
725 725 link_to("Text",
726 726 "/projects/onlinestore/wiki/Start_page",
727 727 :class => "wiki-page"),
728 728 '[[onlinestore:Unknown page]]' =>
729 729 link_to("Unknown page",
730 730 "/projects/onlinestore/wiki/Unknown_page",
731 731 :class => "wiki-page new"),
732 732 # struck through link
733 733 '-[[Another page|Page]]-' =>
734 734 "<del>".html_safe +
735 735 link_to("Page",
736 736 "/projects/ecookbook/wiki/Another_page",
737 737 :class => "wiki-page").html_safe +
738 738 "</del>".html_safe,
739 739 '-[[Another page|Page]] link-' =>
740 740 "<del>".html_safe +
741 741 link_to("Page",
742 742 "/projects/ecookbook/wiki/Another_page",
743 743 :class => "wiki-page").html_safe +
744 744 " link</del>".html_safe,
745 745 # escaping
746 746 '![[Another page|Page]]' => '[[Another page|Page]]',
747 747 # project does not exist
748 748 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
749 749 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
750 750 }
751 751 @project = Project.find(1)
752 752 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
753 753 end
754 754
755 755 def test_wiki_links_within_local_file_generation_context
756 756 to_test = {
757 757 # link to a page
758 758 '[[CookBook documentation]]' =>
759 759 link_to("CookBook documentation", "CookBook_documentation.html",
760 760 :class => "wiki-page"),
761 761 '[[CookBook documentation|documentation]]' =>
762 762 link_to("documentation", "CookBook_documentation.html",
763 763 :class => "wiki-page"),
764 764 '[[CookBook documentation#One-section]]' =>
765 765 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
766 766 :class => "wiki-page"),
767 767 '[[CookBook documentation#One-section|documentation]]' =>
768 768 link_to("documentation", "CookBook_documentation.html#One-section",
769 769 :class => "wiki-page"),
770 770 # page that doesn't exist
771 771 '[[Unknown page]]' =>
772 772 link_to("Unknown page", "Unknown_page.html",
773 773 :class => "wiki-page new"),
774 774 '[[Unknown page|404]]' =>
775 775 link_to("404", "Unknown_page.html",
776 776 :class => "wiki-page new"),
777 777 '[[Unknown page#anchor]]' =>
778 778 link_to("Unknown page", "Unknown_page.html#anchor",
779 779 :class => "wiki-page new"),
780 780 '[[Unknown page#anchor|404]]' =>
781 781 link_to("404", "Unknown_page.html#anchor",
782 782 :class => "wiki-page new"),
783 783 }
784 784 @project = Project.find(1)
785 785 to_test.each do |text, result|
786 786 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
787 787 end
788 788 end
789 789
790 790 def test_wiki_links_within_wiki_page_context
791 791 page = WikiPage.find_by_title('Another_page' )
792 792 to_test = {
793 793 '[[CookBook documentation]]' =>
794 794 link_to("CookBook documentation",
795 795 "/projects/ecookbook/wiki/CookBook_documentation",
796 796 :class => "wiki-page"),
797 797 '[[CookBook documentation|documentation]]' =>
798 798 link_to("documentation",
799 799 "/projects/ecookbook/wiki/CookBook_documentation",
800 800 :class => "wiki-page"),
801 801 '[[CookBook documentation#One-section]]' =>
802 802 link_to("CookBook documentation",
803 803 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
804 804 :class => "wiki-page"),
805 805 '[[CookBook documentation#One-section|documentation]]' =>
806 806 link_to("documentation",
807 807 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
808 808 :class => "wiki-page"),
809 809 # link to the current page
810 810 '[[Another page]]' =>
811 811 link_to("Another page",
812 812 "/projects/ecookbook/wiki/Another_page",
813 813 :class => "wiki-page"),
814 814 '[[Another page|Page]]' =>
815 815 link_to("Page",
816 816 "/projects/ecookbook/wiki/Another_page",
817 817 :class => "wiki-page"),
818 818 '[[Another page#anchor]]' =>
819 819 link_to("Another page",
820 820 "#anchor",
821 821 :class => "wiki-page"),
822 822 '[[Another page#anchor|Page]]' =>
823 823 link_to("Page",
824 824 "#anchor",
825 825 :class => "wiki-page"),
826 826 # page that doesn't exist
827 827 '[[Unknown page]]' =>
828 828 link_to("Unknown page",
829 829 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
830 830 :class => "wiki-page new"),
831 831 '[[Unknown page|404]]' =>
832 832 link_to("404",
833 833 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
834 834 :class => "wiki-page new"),
835 835 '[[Unknown page#anchor]]' =>
836 836 link_to("Unknown page",
837 837 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
838 838 :class => "wiki-page new"),
839 839 '[[Unknown page#anchor|404]]' =>
840 840 link_to("404",
841 841 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
842 842 :class => "wiki-page new"),
843 843 }
844 844 @project = Project.find(1)
845 845 to_test.each do |text, result|
846 846 assert_equal "<p>#{result}</p>",
847 847 textilizable(WikiContent.new( :text => text, :page => page ), :text)
848 848 end
849 849 end
850 850
851 851 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
852 852 to_test = {
853 853 # link to a page
854 854 '[[CookBook documentation]]' =>
855 855 link_to("CookBook documentation",
856 856 "#CookBook_documentation",
857 857 :class => "wiki-page"),
858 858 '[[CookBook documentation|documentation]]' =>
859 859 link_to("documentation",
860 860 "#CookBook_documentation",
861 861 :class => "wiki-page"),
862 862 '[[CookBook documentation#One-section]]' =>
863 863 link_to("CookBook documentation",
864 864 "#CookBook_documentation_One-section",
865 865 :class => "wiki-page"),
866 866 '[[CookBook documentation#One-section|documentation]]' =>
867 867 link_to("documentation",
868 868 "#CookBook_documentation_One-section",
869 869 :class => "wiki-page"),
870 870 # page that doesn't exist
871 871 '[[Unknown page]]' =>
872 872 link_to("Unknown page",
873 873 "#Unknown_page",
874 874 :class => "wiki-page new"),
875 875 '[[Unknown page|404]]' =>
876 876 link_to("404",
877 877 "#Unknown_page",
878 878 :class => "wiki-page new"),
879 879 '[[Unknown page#anchor]]' =>
880 880 link_to("Unknown page",
881 881 "#Unknown_page_anchor",
882 882 :class => "wiki-page new"),
883 883 '[[Unknown page#anchor|404]]' =>
884 884 link_to("404",
885 885 "#Unknown_page_anchor",
886 886 :class => "wiki-page new"),
887 887 }
888 888 @project = Project.find(1)
889 889 to_test.each do |text, result|
890 890 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
891 891 end
892 892 end
893 893
894 894 def test_html_tags
895 895 to_test = {
896 896 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
897 897 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
898 898 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
899 899 # do not escape pre/code tags
900 900 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
901 901 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
902 902 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
903 903 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
904 904 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
905 905 # remove attributes except class
906 906 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
907 907 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
908 908 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
909 909 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
910 910 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
911 911 # xss
912 912 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
913 913 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
914 914 }
915 915 to_test.each { |text, result| assert_equal result, textilizable(text) }
916 916 end
917 917
918 918 def test_allowed_html_tags
919 919 to_test = {
920 920 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
921 921 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
922 922 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
923 923 }
924 924 to_test.each { |text, result| assert_equal result, textilizable(text) }
925 925 end
926 926
927 927 def test_pre_tags
928 928 raw = <<-RAW
929 929 Before
930 930
931 931 <pre>
932 932 <prepared-statement-cache-size>32</prepared-statement-cache-size>
933 933 </pre>
934 934
935 935 After
936 936 RAW
937 937
938 938 expected = <<-EXPECTED
939 939 <p>Before</p>
940 940 <pre>
941 941 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
942 942 </pre>
943 943 <p>After</p>
944 944 EXPECTED
945 945
946 946 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
947 947 end
948 948
949 949 def test_pre_content_should_not_parse_wiki_and_redmine_links
950 950 raw = <<-RAW
951 951 [[CookBook documentation]]
952 952
953 953 #1
954 954
955 955 <pre>
956 956 [[CookBook documentation]]
957 957
958 958 #1
959 959 </pre>
960 960 RAW
961 961
962 962 result1 = link_to("CookBook documentation",
963 963 "/projects/ecookbook/wiki/CookBook_documentation",
964 964 :class => "wiki-page")
965 965 result2 = link_to('#1',
966 966 "/issues/1",
967 967 :class => Issue.find(1).css_classes,
968 968 :title => "Bug: Cannot print recipes (New)")
969 969
970 970 expected = <<-EXPECTED
971 971 <p>#{result1}</p>
972 972 <p>#{result2}</p>
973 973 <pre>
974 974 [[CookBook documentation]]
975 975
976 976 #1
977 977 </pre>
978 978 EXPECTED
979 979
980 980 @project = Project.find(1)
981 981 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
982 982 end
983 983
984 984 def test_non_closing_pre_blocks_should_be_closed
985 985 raw = <<-RAW
986 986 <pre><code>
987 987 RAW
988 988
989 989 expected = <<-EXPECTED
990 990 <pre><code>
991 991 </code></pre>
992 992 EXPECTED
993 993
994 994 @project = Project.find(1)
995 995 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
996 996 end
997 997
998 998 def test_unbalanced_closing_pre_tag_should_not_error
999 999 assert_nothing_raised do
1000 1000 textilizable("unbalanced</pre>")
1001 1001 end
1002 1002 end
1003 1003
1004 1004 def test_syntax_highlight
1005 1005 raw = <<-RAW
1006 1006 <pre><code class="ruby">
1007 1007 # Some ruby code here
1008 1008 </code></pre>
1009 1009 RAW
1010 1010
1011 1011 expected = <<-EXPECTED
1012 1012 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
1013 1013 </code></pre>
1014 1014 EXPECTED
1015 1015
1016 1016 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1017 1017 end
1018 1018
1019 1019 def test_to_path_param
1020 1020 assert_equal 'test1/test2', to_path_param('test1/test2')
1021 1021 assert_equal 'test1/test2', to_path_param('/test1/test2/')
1022 1022 assert_equal 'test1/test2', to_path_param('//test1/test2/')
1023 assert_equal nil, to_path_param('/')
1023 assert_nil to_path_param('/')
1024 1024 end
1025 1025
1026 1026 def test_wiki_links_in_tables
1027 1027 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
1028 1028 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
1029 1029 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1030 1030 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1031 1031 result = "<tr><td>#{link1}</td>" +
1032 1032 "<td>#{link2}</td>" +
1033 1033 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1034 1034 @project = Project.find(1)
1035 1035 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1036 1036 end
1037 1037
1038 1038 def test_text_formatting
1039 1039 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1040 1040 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1041 1041 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1042 1042 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
1043 1043 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
1044 1044 }
1045 1045 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1046 1046 end
1047 1047
1048 1048 def test_wiki_horizontal_rule
1049 1049 assert_equal '<hr />', textilizable('---')
1050 1050 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1051 1051 end
1052 1052
1053 1053 def test_footnotes
1054 1054 raw = <<-RAW
1055 1055 This is some text[1].
1056 1056
1057 1057 fn1. This is the foot note
1058 1058 RAW
1059 1059
1060 1060 expected = <<-EXPECTED
1061 1061 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1062 1062 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1063 1063 EXPECTED
1064 1064
1065 1065 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1066 1066 end
1067 1067
1068 1068 def test_headings
1069 1069 raw = 'h1. Some heading'
1070 1070 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1071 1071
1072 1072 assert_equal expected, textilizable(raw)
1073 1073 end
1074 1074
1075 1075 def test_headings_with_special_chars
1076 1076 # This test makes sure that the generated anchor names match the expected
1077 1077 # ones even if the heading text contains unconventional characters
1078 1078 raw = 'h1. Some heading related to version 0.5'
1079 1079 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1080 1080 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1081 1081
1082 1082 assert_equal expected, textilizable(raw)
1083 1083 end
1084 1084
1085 1085 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1086 1086 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1087 1087 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1088 1088
1089 1089 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1090 1090
1091 1091 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1092 1092 end
1093 1093
1094 1094 def test_table_of_content
1095 1095 set_language_if_valid 'en'
1096 1096
1097 1097 raw = <<-RAW
1098 1098 {{toc}}
1099 1099
1100 1100 h1. Title
1101 1101
1102 1102 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1103 1103
1104 1104 h2. Subtitle with a [[Wiki]] link
1105 1105
1106 1106 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1107 1107
1108 1108 h2. Subtitle with [[Wiki|another Wiki]] link
1109 1109
1110 1110 h2. Subtitle with %{color:red}red text%
1111 1111
1112 1112 <pre>
1113 1113 some code
1114 1114 </pre>
1115 1115
1116 1116 h3. Subtitle with *some* _modifiers_
1117 1117
1118 1118 h3. Subtitle with @inline code@
1119 1119
1120 1120 h1. Another title
1121 1121
1122 1122 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1123 1123
1124 1124 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1125 1125
1126 1126 RAW
1127 1127
1128 1128 expected = '<ul class="toc">' +
1129 1129 '<li><strong>Table of contents</strong></li>' +
1130 1130 '<li><a href="#Title">Title</a>' +
1131 1131 '<ul>' +
1132 1132 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1133 1133 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1134 1134 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1135 1135 '<ul>' +
1136 1136 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1137 1137 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1138 1138 '</ul>' +
1139 1139 '</li>' +
1140 1140 '</ul>' +
1141 1141 '</li>' +
1142 1142 '<li><a href="#Another-title">Another title</a>' +
1143 1143 '<ul>' +
1144 1144 '<li>' +
1145 1145 '<ul>' +
1146 1146 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1147 1147 '</ul>' +
1148 1148 '</li>' +
1149 1149 '<li><a href="#Project-Name">Project Name</a></li>' +
1150 1150 '</ul>' +
1151 1151 '</li>' +
1152 1152 '</ul>'
1153 1153
1154 1154 @project = Project.find(1)
1155 1155 assert textilizable(raw).gsub("\n", "").include?(expected)
1156 1156 end
1157 1157
1158 1158 def test_table_of_content_should_generate_unique_anchors
1159 1159 set_language_if_valid 'en'
1160 1160
1161 1161 raw = <<-RAW
1162 1162 {{toc}}
1163 1163
1164 1164 h1. Title
1165 1165
1166 1166 h2. Subtitle
1167 1167
1168 1168 h2. Subtitle
1169 1169 RAW
1170 1170
1171 1171 expected = '<ul class="toc">' +
1172 1172 '<li><strong>Table of contents</strong></li>' +
1173 1173 '<li><a href="#Title">Title</a>' +
1174 1174 '<ul>' +
1175 1175 '<li><a href="#Subtitle">Subtitle</a></li>' +
1176 1176 '<li><a href="#Subtitle-2">Subtitle</a></li>' +
1177 1177 '</ul>' +
1178 1178 '</li>' +
1179 1179 '</ul>'
1180 1180
1181 1181 @project = Project.find(1)
1182 1182 result = textilizable(raw).gsub("\n", "")
1183 1183 assert_include expected, result
1184 1184 assert_include '<a name="Subtitle">', result
1185 1185 assert_include '<a name="Subtitle-2">', result
1186 1186 end
1187 1187
1188 1188 def test_table_of_content_should_contain_included_page_headings
1189 1189 set_language_if_valid 'en'
1190 1190
1191 1191 raw = <<-RAW
1192 1192 {{toc}}
1193 1193
1194 1194 h1. Included
1195 1195
1196 1196 {{include(Child_1)}}
1197 1197 RAW
1198 1198
1199 1199 expected = '<ul class="toc">' +
1200 1200 '<li><strong>Table of contents</strong></li>' +
1201 1201 '<li><a href="#Included">Included</a></li>' +
1202 1202 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1203 1203 '</ul>'
1204 1204
1205 1205 @project = Project.find(1)
1206 1206 assert textilizable(raw).gsub("\n", "").include?(expected)
1207 1207 end
1208 1208
1209 1209 def test_toc_with_textile_formatting_should_be_parsed
1210 1210 with_settings :text_formatting => 'textile' do
1211 1211 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1212 1212 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1213 1213 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1214 1214 end
1215 1215 end
1216 1216
1217 1217 if Object.const_defined?(:Redcarpet)
1218 1218 def test_toc_with_markdown_formatting_should_be_parsed
1219 1219 with_settings :text_formatting => 'markdown' do
1220 1220 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1221 1221 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1222 1222 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1223 1223 end
1224 1224 end
1225 1225 end
1226 1226
1227 1227 def test_section_edit_links
1228 1228 raw = <<-RAW
1229 1229 h1. Title
1230 1230
1231 1231 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1232 1232
1233 1233 h2. Subtitle with a [[Wiki]] link
1234 1234
1235 1235 h2. Subtitle with *some* _modifiers_
1236 1236
1237 1237 h2. Subtitle with @inline code@
1238 1238
1239 1239 <pre>
1240 1240 some code
1241 1241
1242 1242 h2. heading inside pre
1243 1243
1244 1244 <h2>html heading inside pre</h2>
1245 1245 </pre>
1246 1246
1247 1247 h2. Subtitle after pre tag
1248 1248 RAW
1249 1249
1250 1250 @project = Project.find(1)
1251 1251 set_language_if_valid 'en'
1252 1252 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1253 1253
1254 1254 # heading that contains inline code
1255 1255 assert_match Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-4">' +
1256 1256 '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=4">Edit this section</a></div>' +
1257 1257 '<a name="Subtitle-with-inline-code"></a>' +
1258 1258 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1259 1259 result
1260 1260
1261 1261 # last heading
1262 1262 assert_match Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-5">' +
1263 1263 '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=5">Edit this section</a></div>' +
1264 1264 '<a name="Subtitle-after-pre-tag"></a>' +
1265 1265 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1266 1266 result
1267 1267 end
1268 1268
1269 1269 def test_default_formatter
1270 1270 with_settings :text_formatting => 'unknown' do
1271 1271 text = 'a *link*: http://www.example.net/'
1272 1272 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1273 1273 end
1274 1274 end
1275 1275
1276 1276 def test_parse_redmine_links_should_handle_a_tag_without_attributes
1277 1277 text = '<a>http://example.com</a>'
1278 1278 expected = text.dup
1279 1279 parse_redmine_links(text, nil, nil, nil, true, {})
1280 1280 assert_equal expected, text
1281 1281 end
1282 1282
1283 1283 def test_due_date_distance_in_words
1284 1284 to_test = { Date.today => 'Due in 0 days',
1285 1285 Date.today + 1 => 'Due in 1 day',
1286 1286 Date.today + 100 => 'Due in about 3 months',
1287 1287 Date.today + 20000 => 'Due in over 54 years',
1288 1288 Date.today - 1 => '1 day late',
1289 1289 Date.today - 100 => 'about 3 months late',
1290 1290 Date.today - 20000 => 'over 54 years late',
1291 1291 }
1292 1292 ::I18n.locale = :en
1293 1293 to_test.each do |date, expected|
1294 1294 assert_equal expected, due_date_distance_in_words(date)
1295 1295 end
1296 1296 end
1297 1297
1298 1298 def test_avatar_enabled
1299 1299 with_settings :gravatar_enabled => '1' do
1300 1300 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1301 1301 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1302 1302 # Default size is 50
1303 1303 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1304 1304 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1305 1305 # Non-avatar options should be considered html options
1306 1306 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1307 1307 # The default class of the img tag should be gravatar
1308 1308 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1309 1309 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1310 1310 assert_nil avatar('jsmith')
1311 1311 assert_nil avatar(nil)
1312 1312 end
1313 1313 end
1314 1314
1315 1315 def test_avatar_disabled
1316 1316 with_settings :gravatar_enabled => '0' do
1317 1317 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1318 1318 end
1319 1319 end
1320 1320
1321 1321 def test_link_to_user
1322 1322 user = User.find(2)
1323 1323 result = link_to("John Smith", "/users/2", :class => "user active")
1324 1324 assert_equal result, link_to_user(user)
1325 1325 end
1326 1326
1327 1327 def test_link_to_user_should_not_link_to_locked_user
1328 1328 with_current_user nil do
1329 1329 user = User.find(5)
1330 1330 assert user.locked?
1331 1331 assert_equal 'Dave2 Lopper2', link_to_user(user)
1332 1332 end
1333 1333 end
1334 1334
1335 1335 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1336 1336 with_current_user User.find(1) do
1337 1337 user = User.find(5)
1338 1338 assert user.locked?
1339 1339 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1340 1340 assert_equal result, link_to_user(user)
1341 1341 end
1342 1342 end
1343 1343
1344 1344 def test_link_to_user_should_not_link_to_anonymous
1345 1345 user = User.anonymous
1346 1346 assert user.anonymous?
1347 1347 t = link_to_user(user)
1348 1348 assert_equal ::I18n.t(:label_user_anonymous), t
1349 1349 end
1350 1350
1351 1351 def test_link_to_attachment
1352 1352 a = Attachment.find(3)
1353 1353 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1354 1354 link_to_attachment(a)
1355 1355 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1356 1356 link_to_attachment(a, :text => 'Text')
1357 1357 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1358 1358 assert_equal result,
1359 1359 link_to_attachment(a, :class => 'foo')
1360 1360 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1361 1361 link_to_attachment(a, :download => true)
1362 1362 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1363 1363 link_to_attachment(a, :only_path => false)
1364 1364 end
1365 1365
1366 1366 def test_thumbnail_tag
1367 1367 a = Attachment.find(3)
1368 1368 assert_select_in thumbnail_tag(a),
1369 1369 'a[href=?][title=?] img[alt="3"][src=?]',
1370 1370 "/attachments/3/logo.gif", "logo.gif", "/attachments/thumbnail/3"
1371 1371 end
1372 1372
1373 1373 def test_link_to_project
1374 1374 project = Project.find(1)
1375 1375 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1376 1376 link_to_project(project)
1377 1377 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1378 1378 link_to_project(project, {:only_path => false, :jump => 'blah'})
1379 1379 end
1380 1380
1381 1381 def test_link_to_project_settings
1382 1382 project = Project.find(1)
1383 1383 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1384 1384
1385 1385 project.status = Project::STATUS_CLOSED
1386 1386 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1387 1387
1388 1388 project.status = Project::STATUS_ARCHIVED
1389 1389 assert_equal 'eCookbook', link_to_project_settings(project)
1390 1390 end
1391 1391
1392 1392 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1393 1393 # numeric identifier are no longer allowed
1394 1394 Project.where(:id => 1).update_all(:identifier => 25)
1395 1395 assert_equal '<a href="/projects/1">eCookbook</a>',
1396 1396 link_to_project(Project.find(1))
1397 1397 end
1398 1398
1399 1399 def test_principals_options_for_select_with_users
1400 1400 User.current = nil
1401 1401 users = [User.find(2), User.find(4)]
1402 1402 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1403 1403 principals_options_for_select(users)
1404 1404 end
1405 1405
1406 1406 def test_principals_options_for_select_with_selected
1407 1407 User.current = nil
1408 1408 users = [User.find(2), User.find(4)]
1409 1409 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1410 1410 principals_options_for_select(users, User.find(4))
1411 1411 end
1412 1412
1413 1413 def test_principals_options_for_select_with_users_and_groups
1414 1414 User.current = nil
1415 1415 set_language_if_valid 'en'
1416 1416 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1417 1417 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1418 1418 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1419 1419 principals_options_for_select(users)
1420 1420 end
1421 1421
1422 1422 def test_principals_options_for_select_with_empty_collection
1423 1423 assert_equal '', principals_options_for_select([])
1424 1424 end
1425 1425
1426 1426 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1427 1427 set_language_if_valid 'en'
1428 1428 users = [User.find(2), User.find(4)]
1429 1429 User.current = User.find(4)
1430 1430 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1431 1431 end
1432 1432
1433 1433 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1434 1434 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1435 1435 end
1436 1436
1437 1437 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1438 1438 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1439 1439 end
1440 1440
1441 1441 def test_image_tag_should_pick_the_default_image
1442 1442 assert_match 'src="/images/image.png"', image_tag("image.png")
1443 1443 end
1444 1444
1445 1445 def test_image_tag_should_pick_the_theme_image_if_it_exists
1446 1446 theme = Redmine::Themes.themes.last
1447 1447 theme.images << 'image.png'
1448 1448
1449 1449 with_settings :ui_theme => theme.id do
1450 1450 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1451 1451 assert_match %|src="/images/other.png"|, image_tag("other.png")
1452 1452 end
1453 1453 ensure
1454 1454 theme.images.delete 'image.png'
1455 1455 end
1456 1456
1457 1457 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1458 1458 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1459 1459 end
1460 1460
1461 1461 def test_javascript_include_tag_should_pick_the_default_javascript
1462 1462 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1463 1463 end
1464 1464
1465 1465 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1466 1466 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1467 1467 end
1468 1468
1469 1469 def test_raw_json_should_escape_closing_tags
1470 1470 s = raw_json(["<foo>bar</foo>"])
1471 1471 assert_include '\/foo', s
1472 1472 end
1473 1473
1474 1474 def test_raw_json_should_be_html_safe
1475 1475 s = raw_json(["foo"])
1476 1476 assert s.html_safe?
1477 1477 end
1478 1478
1479 1479 def test_html_title_should_app_title_if_not_set
1480 1480 assert_equal 'Redmine', html_title
1481 1481 end
1482 1482
1483 1483 def test_html_title_should_join_items
1484 1484 html_title 'Foo', 'Bar'
1485 1485 assert_equal 'Foo - Bar - Redmine', html_title
1486 1486 end
1487 1487
1488 1488 def test_html_title_should_append_current_project_name
1489 1489 @project = Project.find(1)
1490 1490 html_title 'Foo', 'Bar'
1491 1491 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1492 1492 end
1493 1493
1494 1494 def test_title_should_return_a_h2_tag
1495 1495 assert_equal '<h2>Foo</h2>', title('Foo')
1496 1496 end
1497 1497
1498 1498 def test_title_should_set_html_title
1499 1499 title('Foo')
1500 1500 assert_equal 'Foo - Redmine', html_title
1501 1501 end
1502 1502
1503 1503 def test_title_should_turn_arrays_into_links
1504 1504 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1505 1505 assert_equal 'Foo - Redmine', html_title
1506 1506 end
1507 1507
1508 1508 def test_title_should_join_items
1509 1509 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1510 1510 assert_equal 'Bar - Foo - Redmine', html_title
1511 1511 end
1512 1512
1513 1513 def test_favicon_path
1514 1514 assert_match %r{^/favicon\.ico}, favicon_path
1515 1515 end
1516 1516
1517 1517 def test_favicon_path_with_suburi
1518 1518 Redmine::Utils.relative_url_root = '/foo'
1519 1519 assert_match %r{^/foo/favicon\.ico}, favicon_path
1520 1520 ensure
1521 1521 Redmine::Utils.relative_url_root = ''
1522 1522 end
1523 1523
1524 1524 def test_favicon_url
1525 1525 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1526 1526 end
1527 1527
1528 1528 def test_favicon_url_with_suburi
1529 1529 Redmine::Utils.relative_url_root = '/foo'
1530 1530 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1531 1531 ensure
1532 1532 Redmine::Utils.relative_url_root = ''
1533 1533 end
1534 1534
1535 1535 def test_truncate_single_line
1536 1536 str = "01234"
1537 1537 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1538 1538 assert_equal "01234 0...", result
1539 1539 assert !result.html_safe?
1540 1540 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1541 1541 assert_equal "01234<&#> 012...", result
1542 1542 assert !result.html_safe?
1543 1543 end
1544 1544
1545 1545 def test_truncate_single_line_non_ascii
1546 1546 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e".force_encoding('UTF-8')
1547 1547 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1548 1548 assert_equal "#{ja} #{ja}...", result
1549 1549 assert !result.html_safe?
1550 1550 end
1551 1551
1552 1552 def test_back_url_should_remove_utf8_checkmark_from_referer
1553 1553 stubs(:request).returns(stub(:env => {'HTTP_REFERER' => "/path?utf8=\u2713&foo=bar"}))
1554 1554 assert_equal "/path?foo=bar", back_url
1555 1555 end
1556 1556
1557 1557 def test_hours_formatting
1558 1558 set_language_if_valid 'en'
1559 1559
1560 1560 with_settings :timespan_format => 'minutes' do
1561 1561 assert_equal '0:45', format_hours(0.75)
1562 1562 assert_equal '0:45 h', l_hours_short(0.75)
1563 1563 assert_equal '0:45 hour', l_hours(0.75)
1564 1564 end
1565 1565 with_settings :timespan_format => 'decimal' do
1566 1566 assert_equal '0.75', format_hours(0.75)
1567 1567 assert_equal '0.75 h', l_hours_short(0.75)
1568 1568 assert_equal '0.75 hour', l_hours(0.75)
1569 1569 end
1570 1570 end
1571 1571
1572 1572 def test_html_hours
1573 1573 assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">:45</span>', html_hours('0:45')
1574 1574 assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">.75</span>', html_hours('0.75')
1575 1575 end
1576 1576 end
@@ -1,3000 +1,3000
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssueTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :email_addresses, :user_preferences, :members, :member_roles, :roles,
22 22 :groups_users,
23 23 :trackers, :projects_trackers,
24 24 :enabled_modules,
25 25 :versions,
26 26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 27 :enumerations,
28 28 :issues, :journals, :journal_details,
29 29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 30 :time_entries
31 31
32 32 include Redmine::I18n
33 33
34 34 def setup
35 35 set_language_if_valid 'en'
36 36 end
37 37
38 38 def teardown
39 39 User.current = nil
40 40 end
41 41
42 42 def test_initialize
43 43 issue = Issue.new
44 44
45 45 assert_nil issue.project_id
46 46 assert_nil issue.tracker_id
47 47 assert_nil issue.status_id
48 48 assert_nil issue.author_id
49 49 assert_nil issue.assigned_to_id
50 50 assert_nil issue.category_id
51 51
52 52 assert_equal IssuePriority.default, issue.priority
53 53 end
54 54
55 55 def test_create
56 56 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
57 57 :status_id => 1, :priority => IssuePriority.all.first,
58 58 :subject => 'test_create',
59 59 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
60 60 assert issue.save
61 61 issue.reload
62 62 assert_equal 1.5, issue.estimated_hours
63 63 end
64 64
65 65 def test_create_minimal
66 66 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create')
67 67 assert issue.save
68 68 assert_equal issue.tracker.default_status, issue.status
69 69 assert issue.description.nil?
70 70 assert_nil issue.estimated_hours
71 71 end
72 72
73 73 def test_create_with_all_fields_disabled
74 74 tracker = Tracker.find(1)
75 75 tracker.core_fields = []
76 76 tracker.save!
77 77
78 78 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create_with_all_fields_disabled')
79 79 assert_save issue
80 80 end
81 81
82 82 def test_start_date_format_should_be_validated
83 83 set_language_if_valid 'en'
84 84 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
85 85 issue = Issue.new(:start_date => invalid_date)
86 86 assert !issue.valid?
87 87 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
88 88 end
89 89 end
90 90
91 91 def test_due_date_format_should_be_validated
92 92 set_language_if_valid 'en'
93 93 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
94 94 issue = Issue.new(:due_date => invalid_date)
95 95 assert !issue.valid?
96 96 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
97 97 end
98 98 end
99 99
100 100 def test_due_date_lesser_than_start_date_should_not_validate
101 101 set_language_if_valid 'en'
102 102 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
103 103 assert !issue.valid?
104 104 assert_include 'Due date must be greater than start date', issue.errors.full_messages
105 105 end
106 106
107 107 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
108 108 issue = Issue.generate(:start_date => '2013-06-04')
109 109 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
110 110 assert !issue.valid?
111 111 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
112 112 end
113 113
114 114 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
115 115 issue = Issue.generate!(:start_date => '2013-06-04')
116 116 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
117 117 issue.start_date = '2013-06-07'
118 118 assert !issue.valid?
119 119 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
120 120 end
121 121
122 122 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
123 123 issue = Issue.generate!(:start_date => '2013-06-04')
124 124 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
125 125 assert issue.valid?
126 126 end
127 127
128 128 def test_estimated_hours_should_be_validated
129 129 set_language_if_valid 'en'
130 130 ['-2'].each do |invalid|
131 131 issue = Issue.new(:estimated_hours => invalid)
132 132 assert !issue.valid?
133 133 assert_include 'Estimated time is invalid', issue.errors.full_messages
134 134 end
135 135 end
136 136
137 137 def test_create_with_required_custom_field
138 138 set_language_if_valid 'en'
139 139 field = IssueCustomField.find_by_name('Database')
140 140 field.update!(:is_required => true)
141 141
142 142 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
143 143 :status_id => 1, :subject => 'test_create',
144 144 :description => 'IssueTest#test_create_with_required_custom_field')
145 145 assert issue.available_custom_fields.include?(field)
146 146 # No value for the custom field
147 147 assert !issue.save
148 148 assert_equal ["Database cannot be blank"], issue.errors.full_messages
149 149 # Blank value
150 150 issue.custom_field_values = { field.id => '' }
151 151 assert !issue.save
152 152 assert_equal ["Database cannot be blank"], issue.errors.full_messages
153 153 # Invalid value
154 154 issue.custom_field_values = { field.id => 'SQLServer' }
155 155 assert !issue.save
156 156 assert_equal ["Database is not included in the list"], issue.errors.full_messages
157 157 # Valid value
158 158 issue.custom_field_values = { field.id => 'PostgreSQL' }
159 159 assert issue.save
160 160 issue.reload
161 161 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
162 162 end
163 163
164 164 def test_create_with_group_assignment
165 165 with_settings :issue_group_assignment => '1' do
166 166 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
167 167 :subject => 'Group assignment',
168 168 :assigned_to_id => 11).save
169 169 issue = Issue.order('id DESC').first
170 170 assert_kind_of Group, issue.assigned_to
171 171 assert_equal Group.find(11), issue.assigned_to
172 172 end
173 173 end
174 174
175 175 def test_create_with_parent_issue_id
176 176 issue = Issue.new(:project_id => 1, :tracker_id => 1,
177 177 :author_id => 1, :subject => 'Group assignment',
178 178 :parent_issue_id => 1)
179 179 assert_save issue
180 180 assert_equal 1, issue.parent_issue_id
181 181 assert_equal Issue.find(1), issue.parent
182 182 end
183 183
184 184 def test_create_with_sharp_parent_issue_id
185 185 issue = Issue.new(:project_id => 1, :tracker_id => 1,
186 186 :author_id => 1, :subject => 'Group assignment',
187 187 :parent_issue_id => "#1")
188 188 assert_save issue
189 189 assert_equal 1, issue.parent_issue_id
190 190 assert_equal Issue.find(1), issue.parent
191 191 end
192 192
193 193 def test_create_with_invalid_parent_issue_id
194 194 set_language_if_valid 'en'
195 195 issue = Issue.new(:project_id => 1, :tracker_id => 1,
196 196 :author_id => 1, :subject => 'Group assignment',
197 197 :parent_issue_id => '01ABC')
198 198 assert !issue.save
199 199 assert_equal '01ABC', issue.parent_issue_id
200 200 assert_include 'Parent task is invalid', issue.errors.full_messages
201 201 end
202 202
203 203 def test_create_with_invalid_sharp_parent_issue_id
204 204 set_language_if_valid 'en'
205 205 issue = Issue.new(:project_id => 1, :tracker_id => 1,
206 206 :author_id => 1, :subject => 'Group assignment',
207 207 :parent_issue_id => '#01ABC')
208 208 assert !issue.save
209 209 assert_equal '#01ABC', issue.parent_issue_id
210 210 assert_include 'Parent task is invalid', issue.errors.full_messages
211 211 end
212 212
213 213 def assert_visibility_match(user, issues)
214 214 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
215 215 end
216 216
217 217 def test_visible_scope_for_anonymous
218 218 # Anonymous user should see issues of public projects only
219 219 issues = Issue.visible(User.anonymous).to_a
220 220 assert issues.any?
221 221 assert_nil issues.detect {|issue| !issue.project.is_public?}
222 222 assert_nil issues.detect {|issue| issue.is_private?}
223 223 assert_visibility_match User.anonymous, issues
224 224 end
225 225
226 226 def test_visible_scope_for_anonymous_without_view_issues_permissions
227 227 # Anonymous user should not see issues without permission
228 228 Role.anonymous.remove_permission!(:view_issues)
229 229 issues = Issue.visible(User.anonymous).to_a
230 230 assert issues.empty?
231 231 assert_visibility_match User.anonymous, issues
232 232 end
233 233
234 234 def test_visible_scope_for_anonymous_without_view_issues_permissions_and_membership
235 235 Role.anonymous.remove_permission!(:view_issues)
236 236 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
237 237
238 238 issues = Issue.visible(User.anonymous).all
239 239 assert issues.any?
240 240 assert_equal [1], issues.map(&:project_id).uniq.sort
241 241 assert_visibility_match User.anonymous, issues
242 242 end
243 243
244 244 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
245 245 Role.anonymous.update!(:issues_visibility => 'default')
246 246 issue = Issue.generate!(:author => User.anonymous, :is_private => true)
247 247 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
248 248 assert !issue.visible?(User.anonymous)
249 249 end
250 250
251 251 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
252 252 assert Role.anonymous.update!(:issues_visibility => 'own')
253 253 issue = Issue.generate!(:author => User.anonymous, :is_private => true)
254 254 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
255 255 assert !issue.visible?(User.anonymous)
256 256 end
257 257
258 258 def test_visible_scope_for_non_member
259 259 user = User.find(9)
260 260 assert user.projects.empty?
261 261 # Non member user should see issues of public projects only
262 262 issues = Issue.visible(user).to_a
263 263 assert issues.any?
264 264 assert_nil issues.detect {|issue| !issue.project.is_public?}
265 265 assert_nil issues.detect {|issue| issue.is_private?}
266 266 assert_visibility_match user, issues
267 267 end
268 268
269 269 def test_visible_scope_for_non_member_with_own_issues_visibility
270 270 Role.non_member.update! :issues_visibility => 'own'
271 271 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
272 272 user = User.find(9)
273 273
274 274 issues = Issue.visible(user).to_a
275 275 assert issues.any?
276 276 assert_nil issues.detect {|issue| issue.author != user}
277 277 assert_visibility_match user, issues
278 278 end
279 279
280 280 def test_visible_scope_for_non_member_without_view_issues_permissions
281 281 # Non member user should not see issues without permission
282 282 Role.non_member.remove_permission!(:view_issues)
283 283 user = User.find(9)
284 284 assert user.projects.empty?
285 285 issues = Issue.visible(user).to_a
286 286 assert issues.empty?
287 287 assert_visibility_match user, issues
288 288 end
289 289
290 290 def test_visible_scope_for_non_member_without_view_issues_permissions_and_membership
291 291 Role.non_member.remove_permission!(:view_issues)
292 292 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
293 293 user = User.find(9)
294 294
295 295 issues = Issue.visible(user).all
296 296 assert issues.any?
297 297 assert_equal [1], issues.map(&:project_id).uniq.sort
298 298 assert_visibility_match user, issues
299 299 end
300 300
301 301 def test_visible_scope_for_member
302 302 user = User.find(9)
303 303 # User should see issues of projects for which user has view_issues permissions only
304 304 Role.non_member.remove_permission!(:view_issues)
305 305 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
306 306 issues = Issue.visible(user).to_a
307 307 assert issues.any?
308 308 assert_nil issues.detect {|issue| issue.project_id != 3}
309 309 assert_nil issues.detect {|issue| issue.is_private?}
310 310 assert_visibility_match user, issues
311 311 end
312 312
313 313 def test_visible_scope_for_member_without_view_issues_permission_and_non_member_role_having_the_permission
314 314 Role.non_member.add_permission!(:view_issues)
315 315 Role.find(1).remove_permission!(:view_issues)
316 316 user = User.find(2)
317 317
318 318 assert_equal 0, Issue.where(:project_id => 1).visible(user).count
319 319 assert_equal false, Issue.where(:project_id => 1).first.visible?(user)
320 320 end
321 321
322 322 def test_visible_scope_with_custom_non_member_role_having_restricted_permission
323 323 role = Role.generate!(:permissions => [:view_project])
324 324 assert Role.non_member.has_permission?(:view_issues)
325 325 user = User.generate!
326 326 Member.create!(:principal => Group.non_member, :project_id => 1, :roles => [role])
327 327
328 328 issues = Issue.visible(user).to_a
329 329 assert issues.any?
330 330 assert_nil issues.detect {|issue| issue.project_id == 1}
331 331 end
332 332
333 333 def test_visible_scope_with_custom_non_member_role_having_extended_permission
334 334 role = Role.generate!(:permissions => [:view_project, :view_issues])
335 335 Role.non_member.remove_permission!(:view_issues)
336 336 user = User.generate!
337 337 Member.create!(:principal => Group.non_member, :project_id => 1, :roles => [role])
338 338
339 339 issues = Issue.visible(user).to_a
340 340 assert issues.any?
341 341 assert_not_nil issues.detect {|issue| issue.project_id == 1}
342 342 end
343 343
344 344 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
345 345 user = User.find(8)
346 346 assert user.groups.any?
347 347 group = user.groups.first
348 348 Member.create!(:principal => group, :project_id => 1, :role_ids => [2])
349 349 Role.non_member.remove_permission!(:view_issues)
350 350
351 351 with_settings :issue_group_assignment => '1' do
352 352 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 3,
353 353 :status_id => 1, :priority => IssuePriority.all.first,
354 354 :subject => 'Assignment test',
355 355 :assigned_to => group,
356 356 :is_private => true)
357 357
358 358 Role.find(2).update! :issues_visibility => 'default'
359 359 issues = Issue.visible(User.find(8)).to_a
360 360 assert issues.any?
361 361 assert issues.include?(issue)
362 362
363 363 Role.find(2).update! :issues_visibility => 'own'
364 364 issues = Issue.visible(User.find(8)).to_a
365 365 assert issues.any?
366 366 assert_include issue, issues
367 367 end
368 368 end
369 369
370 370 def test_visible_scope_for_member_with_limited_tracker_ids
371 371 role = Role.find(1)
372 372 role.set_permission_trackers :view_issues, [2]
373 373 role.save!
374 374 user = User.find(2)
375 375
376 376 issues = Issue.where(:project_id => 1).visible(user).to_a
377 377 assert issues.any?
378 378 assert_equal [2], issues.map(&:tracker_id).uniq
379 379
380 380 assert Issue.where(:project_id => 1).all? {|issue| issue.visible?(user) ^ issue.tracker_id != 2}
381 381 end
382 382
383 383 def test_visible_scope_should_consider_tracker_ids_on_each_project
384 384 user = User.generate!
385 385
386 386 project1 = Project.generate!
387 387 role1 = Role.generate!
388 388 role1.add_permission! :view_issues
389 389 role1.set_permission_trackers :view_issues, :all
390 390 role1.save!
391 391 User.add_to_project(user, project1, role1)
392 392
393 393 project2 = Project.generate!
394 394 role2 = Role.generate!
395 395 role2.add_permission! :view_issues
396 396 role2.set_permission_trackers :view_issues, [2]
397 397 role2.save!
398 398 User.add_to_project(user, project2, role2)
399 399
400 400 visible_issues = [
401 401 Issue.generate!(:project => project1, :tracker_id => 1),
402 402 Issue.generate!(:project => project1, :tracker_id => 2),
403 403 Issue.generate!(:project => project2, :tracker_id => 2)
404 404 ]
405 405 hidden_issue = Issue.generate!(:project => project2, :tracker_id => 1)
406 406
407 407 issues = Issue.where(:project_id => [project1.id, project2.id]).visible(user)
408 408 assert_equal visible_issues.map(&:id), issues.ids.sort
409 409
410 410 assert visible_issues.all? {|issue| issue.visible?(user)}
411 411 assert !hidden_issue.visible?(user)
412 412 end
413 413
414 414 def test_visible_scope_should_not_consider_roles_without_view_issues_permission
415 415 user = User.generate!
416 416 role1 = Role.generate!
417 417 role1.remove_permission! :view_issues
418 418 role1.set_permission_trackers :view_issues, :all
419 419 role1.save!
420 420 role2 = Role.generate!
421 421 role2.add_permission! :view_issues
422 422 role2.set_permission_trackers :view_issues, [2]
423 423 role2.save!
424 424 User.add_to_project(user, Project.find(1), [role1, role2])
425 425
426 426 issues = Issue.where(:project_id => 1).visible(user).to_a
427 427 assert issues.any?
428 428 assert_equal [2], issues.map(&:tracker_id).uniq
429 429
430 430 assert Issue.where(:project_id => 1).all? {|issue| issue.visible?(user) ^ issue.tracker_id != 2}
431 431 end
432 432
433 433 def test_visible_scope_for_admin
434 434 user = User.find(1)
435 435 user.members.each(&:destroy)
436 436 assert user.projects.empty?
437 437 issues = Issue.visible(user).to_a
438 438 assert issues.any?
439 439 # Admin should see issues on private projects that admin does not belong to
440 440 assert issues.detect {|issue| !issue.project.is_public?}
441 441 # Admin should see private issues of other users
442 442 assert issues.detect {|issue| issue.is_private? && issue.author != user}
443 443 assert_visibility_match user, issues
444 444 end
445 445
446 446 def test_visible_scope_with_project
447 447 project = Project.find(1)
448 448 issues = Issue.visible(User.find(2), :project => project).to_a
449 449 projects = issues.collect(&:project).uniq
450 450 assert_equal 1, projects.size
451 451 assert_equal project, projects.first
452 452 end
453 453
454 454 def test_visible_scope_with_project_and_subprojects
455 455 project = Project.find(1)
456 456 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).to_a
457 457 projects = issues.collect(&:project).uniq
458 458 assert projects.size > 1
459 459 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
460 460 end
461 461
462 462 def test_visible_and_nested_set_scopes
463 463 user = User.generate!
464 464 Member.create!(:project_id => 1, :principal => user, :role_ids => [1])
465 465 parent = Issue.generate!(:assigned_to => user)
466 466 assert parent.visible?(user)
467 467 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
468 468 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
469 469 parent.reload
470 470 child1.reload
471 471 child2.reload
472 472 assert child1.visible?(user)
473 473 assert child2.visible?(user)
474 474 assert_equal 2, parent.descendants.count
475 475 assert_equal 2, parent.descendants.visible(user).count
476 476 # awesome_nested_set 2-1-stable branch has regression.
477 477 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
478 478 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
479 479 assert_equal 2, parent.descendants.collect{|i| i}.size
480 480 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
481 481 end
482 482
483 483 def test_visible_scope_with_unsaved_user_should_not_raise_an_error
484 484 user = User.new
485 485 assert_nothing_raised do
486 486 Issue.visible(user).to_a
487 487 end
488 488 end
489 489
490 490 def test_open_scope
491 491 issues = Issue.open.to_a
492 492 assert_nil issues.detect(&:closed?)
493 493 end
494 494
495 495 def test_open_scope_with_arg
496 496 issues = Issue.open(false).to_a
497 497 assert_equal issues, issues.select(&:closed?)
498 498 end
499 499
500 500 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
501 501 version = Version.find(2)
502 502 assert version.fixed_issues.any?
503 503 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
504 504 end
505 505
506 506 def test_fixed_version_scope_with_empty_array_should_return_no_result
507 507 assert_equal 0, Issue.fixed_version([]).count
508 508 end
509 509
510 510 def test_assigned_to_scope_should_return_issues_assigned_to_the_user
511 511 user = User.generate!
512 512 issue = Issue.generate!
513 513 Issue.where(:id => issue.id).update_all :assigned_to_id => user.id
514 514 assert_equal [issue], Issue.assigned_to(user).to_a
515 515 end
516 516
517 517 def test_assigned_to_scope_should_return_issues_assigned_to_the_user_groups
518 518 group = Group.generate!
519 519 user = User.generate!
520 520 group.users << user
521 521 issue = Issue.generate!
522 522 Issue.where(:id => issue.id).update_all :assigned_to_id => group.id
523 523 assert_equal [issue], Issue.assigned_to(user).to_a
524 524 end
525 525
526 526 def test_issue_should_be_readonly_on_closed_project
527 527 issue = Issue.find(1)
528 528 user = User.find(1)
529 529
530 530 assert_equal true, issue.visible?(user)
531 531 assert_equal true, issue.editable?(user)
532 532 assert_equal true, issue.deletable?(user)
533 533
534 534 issue.project.close
535 535 issue.reload
536 536
537 537 assert_equal true, issue.visible?(user)
538 538 assert_equal false, issue.editable?(user)
539 539 assert_equal false, issue.deletable?(user)
540 540 end
541 541
542 542 def test_errors_full_messages_should_include_custom_fields_errors
543 543 field = IssueCustomField.find_by_name('Database')
544 544
545 545 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
546 546 :status_id => 1, :subject => 'test_create',
547 547 :description => 'IssueTest#test_create_with_required_custom_field')
548 548 assert issue.available_custom_fields.include?(field)
549 549 # Invalid value
550 550 issue.custom_field_values = { field.id => 'SQLServer' }
551 551
552 552 assert !issue.valid?
553 553 assert_equal 1, issue.errors.full_messages.size
554 554 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
555 555 issue.errors.full_messages.first
556 556 end
557 557
558 558 def test_update_issue_with_required_custom_field
559 559 field = IssueCustomField.find_by_name('Database')
560 560 field.update!(:is_required => true)
561 561
562 562 issue = Issue.find(1)
563 563 assert_nil issue.custom_value_for(field)
564 564 assert issue.available_custom_fields.include?(field)
565 565 # No change to custom values, issue can be saved
566 566 assert issue.save
567 567 # Blank value
568 568 issue.custom_field_values = { field.id => '' }
569 569 assert !issue.save
570 570 # Valid value
571 571 issue.custom_field_values = { field.id => 'PostgreSQL' }
572 572 assert issue.save
573 573 issue.reload
574 574 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
575 575 end
576 576
577 577 def test_should_not_update_attributes_if_custom_fields_validation_fails
578 578 issue = Issue.find(1)
579 579 field = IssueCustomField.find_by_name('Database')
580 580 assert issue.available_custom_fields.include?(field)
581 581
582 582 issue.custom_field_values = { field.id => 'Invalid' }
583 583 issue.subject = 'Should be not be saved'
584 584 assert !issue.save
585 585
586 586 issue.reload
587 587 assert_equal "Cannot print recipes", issue.subject
588 588 end
589 589
590 590 def test_should_not_recreate_custom_values_objects_on_update
591 591 field = IssueCustomField.find_by_name('Database')
592 592
593 593 issue = Issue.find(1)
594 594 issue.custom_field_values = { field.id => 'PostgreSQL' }
595 595 assert issue.save
596 596 custom_value = issue.custom_value_for(field)
597 597 issue.reload
598 598 issue.custom_field_values = { field.id => 'MySQL' }
599 599 assert issue.save
600 600 issue.reload
601 601 assert_equal custom_value.id, issue.custom_value_for(field).id
602 602 end
603 603
604 604 def test_setting_project_should_set_version_to_default_version
605 605 version = Version.generate!(:project_id => 1)
606 606 Project.find(1).update!(:default_version_id => version.id)
607 607
608 608 issue = Issue.new(:project_id => 1)
609 609 assert_equal version, issue.fixed_version
610 610 end
611 611
612 612 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
613 613 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
614 614 :status_id => 1, :subject => 'Test',
615 615 :custom_field_values => {'2' => 'Test'})
616 616 assert !Tracker.find(2).custom_field_ids.include?(2)
617 617
618 618 issue = Issue.find(issue.id)
619 619 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
620 620
621 621 issue = Issue.find(issue.id)
622 622 custom_value = issue.custom_value_for(2)
623 623 assert_not_nil custom_value
624 624 assert_equal 'Test', custom_value.value
625 625 end
626 626
627 627 def test_assigning_tracker_id_should_reload_custom_fields_values
628 628 issue = Issue.new(:project => Project.find(1))
629 629 assert issue.custom_field_values.empty?
630 630 issue.tracker_id = 1
631 631 assert issue.custom_field_values.any?
632 632 end
633 633
634 634 def test_assigning_attributes_should_assign_project_and_tracker_first
635 635 seq = sequence('seq')
636 636 issue = Issue.new
637 637 issue.expects(:project_id=).in_sequence(seq)
638 638 issue.expects(:tracker_id=).in_sequence(seq)
639 639 issue.expects(:subject=).in_sequence(seq)
640 640 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
641 641 end
642 642
643 643 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
644 644 attributes = ActiveSupport::OrderedHash.new
645 645 attributes['custom_field_values'] = { '1' => 'MySQL' }
646 646 attributes['tracker_id'] = '1'
647 647 issue = Issue.new(:project => Project.find(1))
648 648 issue.attributes = attributes
649 649 assert_equal 'MySQL', issue.custom_field_value(1)
650 650 end
651 651
652 652 def test_changing_tracker_should_clear_disabled_core_fields
653 653 tracker = Tracker.find(2)
654 654 tracker.core_fields = tracker.core_fields - %w(due_date)
655 655 tracker.save!
656 656
657 657 issue = Issue.generate!(:tracker_id => 1, :start_date => Date.today, :due_date => Date.today)
658 658 issue.save!
659 659
660 660 issue.tracker_id = 2
661 661 issue.save!
662 662 assert_not_nil issue.start_date
663 663 assert_nil issue.due_date
664 664 end
665 665
666 666 def test_attribute_cleared_on_tracker_change_should_be_journalized
667 667 CustomField.delete_all
668 668 tracker = Tracker.find(2)
669 669 tracker.core_fields = tracker.core_fields - %w(due_date)
670 670 tracker.save!
671 671
672 672 issue = Issue.generate!(:tracker_id => 1, :due_date => Date.today)
673 673 issue.save!
674 674
675 675 assert_difference 'Journal.count' do
676 676 issue.init_journal User.find(1)
677 677 issue.tracker_id = 2
678 678 issue.save!
679 679 assert_nil issue.due_date
680 680 end
681 681 journal = Journal.order('id DESC').first
682 682 details = journal.details.select {|d| d.prop_key == 'due_date'}
683 683 assert_equal 1, details.count
684 684 end
685 685
686 686 def test_reload_should_reload_custom_field_values
687 687 issue = Issue.generate!
688 688 issue.custom_field_values = {'2' => 'Foo'}
689 689 issue.save!
690 690
691 691 issue = Issue.order('id desc').first
692 692 assert_equal 'Foo', issue.custom_field_value(2)
693 693
694 694 issue.custom_field_values = {'2' => 'Bar'}
695 695 assert_equal 'Bar', issue.custom_field_value(2)
696 696
697 697 issue.reload
698 698 assert_equal 'Foo', issue.custom_field_value(2)
699 699 end
700 700
701 701 def test_should_update_issue_with_disabled_tracker
702 702 p = Project.find(1)
703 703 issue = Issue.find(1)
704 704
705 705 p.trackers.delete(issue.tracker)
706 706 assert !p.trackers.include?(issue.tracker)
707 707
708 708 issue.reload
709 709 issue.subject = 'New subject'
710 710 assert issue.save
711 711 end
712 712
713 713 def test_should_not_set_a_disabled_tracker
714 714 p = Project.find(1)
715 715 p.trackers.delete(Tracker.find(2))
716 716
717 717 issue = Issue.find(1)
718 718 issue.tracker_id = 2
719 719 issue.subject = 'New subject'
720 720 assert !issue.save
721 721 assert_not_equal [], issue.errors[:tracker_id]
722 722 end
723 723
724 724 def test_category_based_assignment
725 725 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
726 726 :status_id => 1, :priority => IssuePriority.all.first,
727 727 :subject => 'Assignment test',
728 728 :description => 'Assignment test', :category_id => 1)
729 729 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
730 730 end
731 731
732 732 def test_new_statuses_allowed_to
733 733 WorkflowTransition.delete_all
734 734 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
735 735 :old_status_id => 1, :new_status_id => 2,
736 736 :author => false, :assignee => false)
737 737 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
738 738 :old_status_id => 1, :new_status_id => 3,
739 739 :author => true, :assignee => false)
740 740 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
741 741 :old_status_id => 1, :new_status_id => 4,
742 742 :author => false, :assignee => true)
743 743 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
744 744 :old_status_id => 1, :new_status_id => 5,
745 745 :author => true, :assignee => true)
746 746 status = IssueStatus.find(1)
747 747 role = Role.find(1)
748 748 tracker = Tracker.find(1)
749 749 user = User.find(2)
750 750
751 751 issue = Issue.generate!(:tracker => tracker, :status => status,
752 752 :project_id => 1, :author_id => 1)
753 753 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
754 754
755 755 issue = Issue.generate!(:tracker => tracker, :status => status,
756 756 :project_id => 1, :author => user)
757 757 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
758 758
759 759 issue = Issue.generate!(:tracker => tracker, :status => status,
760 760 :project_id => 1, :author_id => 1,
761 761 :assigned_to => user)
762 762 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
763 763
764 764 issue = Issue.generate!(:tracker => tracker, :status => status,
765 765 :project_id => 1, :author => user,
766 766 :assigned_to => user)
767 767 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
768 768 end
769 769
770 770 def test_new_statuses_allowed_to_should_consider_group_assignment
771 771 WorkflowTransition.delete_all
772 772 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
773 773 :old_status_id => 1, :new_status_id => 4,
774 774 :author => false, :assignee => true)
775 775 group = Group.generate!
776 776 Member.create!(:project_id => 1, :principal => group, :role_ids => [1])
777 777
778 778 user = User.find(2)
779 779 group.users << user
780 780
781 781 with_settings :issue_group_assignment => '1' do
782 782 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
783 783 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
784 784 end
785 785 end
786 786
787 787 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
788 788 admin = User.find(1)
789 789 issue = Issue.find(1)
790 790 assert !admin.member_of?(issue.project)
791 791 expected_statuses = [issue.status] +
792 792 WorkflowTransition.where(:old_status_id => issue.status_id).
793 793 map(&:new_status).uniq.sort
794 794 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
795 795 end
796 796
797 797 def test_new_statuses_allowed_to_should_return_allowed_statuses_and_current_status_when_copying
798 798 Tracker.find(1).generate_transitions! :role_id => 1, :clear => true, 0 => [1, 3]
799 799
800 800 orig = Issue.generate!(:project_id => 1, :tracker_id => 1, :status_id => 4)
801 801 issue = orig.copy
802 802 assert_equal [1, 3, 4], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
803 803 assert_equal 4, issue.status_id
804 804 end
805 805
806 806 def test_safe_attributes_names_should_not_include_disabled_field
807 807 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
808 808
809 809 issue = Issue.new(:tracker => tracker)
810 810 assert_include 'tracker_id', issue.safe_attribute_names
811 811 assert_include 'status_id', issue.safe_attribute_names
812 812 assert_include 'subject', issue.safe_attribute_names
813 813 assert_include 'description', issue.safe_attribute_names
814 814 assert_include 'custom_field_values', issue.safe_attribute_names
815 815 assert_include 'custom_fields', issue.safe_attribute_names
816 816 assert_include 'lock_version', issue.safe_attribute_names
817 817
818 818 tracker.core_fields.each do |field|
819 819 assert_include field, issue.safe_attribute_names
820 820 end
821 821
822 822 tracker.disabled_core_fields.each do |field|
823 823 assert_not_include field, issue.safe_attribute_names
824 824 end
825 825 end
826 826
827 827 def test_safe_attributes_should_ignore_disabled_fields
828 828 tracker = Tracker.find(1)
829 829 tracker.core_fields = %w(assigned_to_id due_date)
830 830 tracker.save!
831 831
832 832 issue = Issue.new(:tracker => tracker)
833 833 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
834 834 assert_nil issue.start_date
835 835 assert_equal Date.parse('2012-07-14'), issue.due_date
836 836 end
837 837
838 838 def test_safe_attributes_should_accept_target_tracker_enabled_fields
839 839 source = Tracker.find(1)
840 840 source.core_fields = []
841 841 source.save!
842 842 target = Tracker.find(2)
843 843 target.core_fields = %w(assigned_to_id due_date)
844 844 target.save!
845 845 user = User.find(2)
846 846
847 847 issue = Issue.new(:project => Project.find(1), :tracker => source)
848 848 issue.send :safe_attributes=, {'tracker_id' => 2, 'due_date' => '2012-07-14'}, user
849 849 assert_equal target, issue.tracker
850 850 assert_equal Date.parse('2012-07-14'), issue.due_date
851 851 end
852 852
853 853 def test_safe_attributes_should_not_include_readonly_fields
854 854 WorkflowPermission.delete_all
855 855 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
856 856 :role_id => 1, :field_name => 'due_date',
857 857 :rule => 'readonly')
858 858 user = User.find(2)
859 859
860 860 issue = Issue.new(:project_id => 1, :tracker_id => 1)
861 861 assert_equal %w(due_date), issue.read_only_attribute_names(user)
862 862 assert_not_include 'due_date', issue.safe_attribute_names(user)
863 863
864 864 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
865 865 assert_equal Date.parse('2012-07-14'), issue.start_date
866 866 assert_nil issue.due_date
867 867 end
868 868
869 869 def test_safe_attributes_should_not_include_readonly_custom_fields
870 870 cf1 = IssueCustomField.create!(:name => 'Writable field',
871 871 :field_format => 'string',
872 872 :is_for_all => true, :tracker_ids => [1])
873 873 cf2 = IssueCustomField.create!(:name => 'Readonly field',
874 874 :field_format => 'string',
875 875 :is_for_all => true, :tracker_ids => [1])
876 876 WorkflowPermission.delete_all
877 877 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
878 878 :role_id => 1, :field_name => cf2.id.to_s,
879 879 :rule => 'readonly')
880 880 user = User.find(2)
881 881 issue = Issue.new(:project_id => 1, :tracker_id => 1)
882 882 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
883 883 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
884 884
885 885 issue.send :safe_attributes=, {'custom_field_values' => {
886 886 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
887 887 }}, user
888 888 assert_equal 'value1', issue.custom_field_value(cf1)
889 889 assert_nil issue.custom_field_value(cf2)
890 890
891 891 issue.send :safe_attributes=, {'custom_fields' => [
892 892 {'id' => cf1.id.to_s, 'value' => 'valuea'},
893 893 {'id' => cf2.id.to_s, 'value' => 'valueb'}
894 894 ]}, user
895 895 assert_equal 'valuea', issue.custom_field_value(cf1)
896 896 assert_nil issue.custom_field_value(cf2)
897 897 end
898 898
899 899 def test_editable_custom_field_values_should_return_non_readonly_custom_values
900 900 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
901 901 :is_for_all => true, :tracker_ids => [1, 2])
902 902 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
903 903 :is_for_all => true, :tracker_ids => [1, 2])
904 904 WorkflowPermission.delete_all
905 905 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
906 906 :field_name => cf2.id.to_s, :rule => 'readonly')
907 907 user = User.find(2)
908 908
909 909 issue = Issue.new(:project_id => 1, :tracker_id => 1)
910 910 values = issue.editable_custom_field_values(user)
911 911 assert values.detect {|value| value.custom_field == cf1}
912 912 assert_nil values.detect {|value| value.custom_field == cf2}
913 913
914 914 issue.tracker_id = 2
915 915 values = issue.editable_custom_field_values(user)
916 916 assert values.detect {|value| value.custom_field == cf1}
917 917 assert values.detect {|value| value.custom_field == cf2}
918 918 end
919 919
920 920 def test_editable_custom_fields_should_return_custom_field_that_is_enabled_for_the_role_only
921 921 enabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
922 922 disabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
923 923 user = User.find(2)
924 924 issue = Issue.new(:project_id => 1, :tracker_id => 1)
925 925
926 926 assert_include enabled_cf, issue.editable_custom_fields(user)
927 927 assert_not_include disabled_cf, issue.editable_custom_fields(user)
928 928 end
929 929
930 930 def test_safe_attributes_should_accept_target_tracker_writable_fields
931 931 WorkflowPermission.delete_all
932 932 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
933 933 :role_id => 1, :field_name => 'due_date',
934 934 :rule => 'readonly')
935 935 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
936 936 :role_id => 1, :field_name => 'start_date',
937 937 :rule => 'readonly')
938 938 user = User.find(2)
939 939
940 940 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
941 941
942 942 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
943 943 'due_date' => '2012-07-14'}, user
944 944 assert_equal Date.parse('2012-07-12'), issue.start_date
945 945 assert_nil issue.due_date
946 946
947 947 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
948 948 'due_date' => '2012-07-16',
949 949 'tracker_id' => 2}, user
950 950 assert_equal Date.parse('2012-07-12'), issue.start_date
951 951 assert_equal Date.parse('2012-07-16'), issue.due_date
952 952 end
953 953
954 954 def test_safe_attributes_should_accept_target_status_writable_fields
955 955 WorkflowPermission.delete_all
956 956 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
957 957 :role_id => 1, :field_name => 'due_date',
958 958 :rule => 'readonly')
959 959 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
960 960 :role_id => 1, :field_name => 'start_date',
961 961 :rule => 'readonly')
962 962 user = User.find(2)
963 963
964 964 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
965 965
966 966 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
967 967 'due_date' => '2012-07-14'},
968 968 user
969 969 assert_equal Date.parse('2012-07-12'), issue.start_date
970 970 assert_nil issue.due_date
971 971
972 972 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
973 973 'due_date' => '2012-07-16',
974 974 'status_id' => 2},
975 975 user
976 976 assert_equal Date.parse('2012-07-12'), issue.start_date
977 977 assert_equal Date.parse('2012-07-16'), issue.due_date
978 978 end
979 979
980 980 def test_required_attributes_should_be_validated
981 981 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
982 982 :is_for_all => true, :tracker_ids => [1, 2])
983 983
984 984 WorkflowPermission.delete_all
985 985 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
986 986 :role_id => 1, :field_name => 'due_date',
987 987 :rule => 'required')
988 988 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
989 989 :role_id => 1, :field_name => 'category_id',
990 990 :rule => 'required')
991 991 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
992 992 :role_id => 1, :field_name => cf.id.to_s,
993 993 :rule => 'required')
994 994
995 995 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
996 996 :role_id => 1, :field_name => 'start_date',
997 997 :rule => 'required')
998 998 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
999 999 :role_id => 1, :field_name => cf.id.to_s,
1000 1000 :rule => 'required')
1001 1001 user = User.find(2)
1002 1002
1003 1003 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1004 1004 :status_id => 1, :subject => 'Required fields',
1005 1005 :author => user)
1006 1006 assert_equal [cf.id.to_s, "category_id", "due_date"],
1007 1007 issue.required_attribute_names(user).sort
1008 1008 assert !issue.save, "Issue was saved"
1009 1009 assert_equal ["Category cannot be blank", "Due date cannot be blank", "Foo cannot be blank"],
1010 1010 issue.errors.full_messages.sort
1011 1011
1012 1012 issue.tracker_id = 2
1013 1013 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
1014 1014 assert !issue.save, "Issue was saved"
1015 1015 assert_equal ["Foo cannot be blank", "Start date cannot be blank"],
1016 1016 issue.errors.full_messages.sort
1017 1017
1018 1018 issue.start_date = Date.today
1019 1019 issue.custom_field_values = {cf.id.to_s => 'bar'}
1020 1020 assert issue.save
1021 1021 end
1022 1022
1023 1023 def test_required_attribute_that_is_disabled_for_the_tracker_should_not_be_required
1024 1024 WorkflowPermission.delete_all
1025 1025 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1026 1026 :role_id => 1, :field_name => 'start_date',
1027 1027 :rule => 'required')
1028 1028 user = User.find(2)
1029 1029
1030 1030 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1031 1031 :subject => 'Required fields', :author => user)
1032 1032 assert !issue.save
1033 1033 assert_include "Start date cannot be blank", issue.errors.full_messages
1034 1034
1035 1035 tracker = Tracker.find(1)
1036 1036 tracker.core_fields -= %w(start_date)
1037 1037 tracker.save!
1038 1038 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1039 1039 :subject => 'Required fields', :author => user)
1040 1040 assert issue.save
1041 1041 end
1042 1042
1043 1043 def test_category_should_not_be_required_if_project_has_no_categories
1044 1044 Project.find(1).issue_categories.delete_all
1045 1045 WorkflowPermission.delete_all
1046 1046 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1047 1047 :role_id => 1, :field_name => 'category_id',:rule => 'required')
1048 1048 user = User.find(2)
1049 1049
1050 1050 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1051 1051 :subject => 'Required fields', :author => user)
1052 1052 assert_save issue
1053 1053 end
1054 1054
1055 1055 def test_fixed_version_should_not_be_required_no_assignable_versions
1056 1056 Version.delete_all
1057 1057 WorkflowPermission.delete_all
1058 1058 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1059 1059 :role_id => 1, :field_name => 'fixed_version_id',:rule => 'required')
1060 1060 user = User.find(2)
1061 1061
1062 1062 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1063 1063 :subject => 'Required fields', :author => user)
1064 1064 assert_save issue
1065 1065 end
1066 1066
1067 1067 def test_required_custom_field_that_is_not_visible_for_the_user_should_not_be_required
1068 1068 CustomField.delete_all
1069 1069 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
1070 1070 user = User.generate!
1071 1071 User.add_to_project(user, Project.find(1), Role.find(2))
1072 1072
1073 1073 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1074 1074 :subject => 'Required fields', :author => user)
1075 1075 assert_save issue
1076 1076 end
1077 1077
1078 1078 def test_required_custom_field_that_is_visible_for_the_user_should_be_required
1079 1079 CustomField.delete_all
1080 1080 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
1081 1081 user = User.generate!
1082 1082 User.add_to_project(user, Project.find(1), Role.find(1))
1083 1083
1084 1084 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1085 1085 :subject => 'Required fields', :author => user)
1086 1086 assert !issue.save
1087 1087 assert_include "#{field.name} cannot be blank", issue.errors.full_messages
1088 1088 end
1089 1089
1090 1090 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
1091 1091 WorkflowPermission.delete_all
1092 1092 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1093 1093 :role_id => 1, :field_name => 'due_date',
1094 1094 :rule => 'required')
1095 1095 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1096 1096 :role_id => 1, :field_name => 'start_date',
1097 1097 :rule => 'required')
1098 1098 user = User.find(2)
1099 1099 member = Member.find(1)
1100 1100 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1101 1101
1102 1102 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
1103 1103
1104 1104 member.role_ids = [1, 2]
1105 1105 member.save!
1106 1106 assert_equal [], issue.required_attribute_names(user.reload)
1107 1107
1108 1108 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1109 1109 :role_id => 2, :field_name => 'due_date',
1110 1110 :rule => 'required')
1111 1111 assert_equal %w(due_date), issue.required_attribute_names(user)
1112 1112
1113 1113 member.role_ids = [1, 2, 3]
1114 1114 member.save!
1115 1115 assert_equal [], issue.required_attribute_names(user.reload)
1116 1116
1117 1117 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1118 1118 :role_id => 3, :field_name => 'due_date',
1119 1119 :rule => 'readonly')
1120 1120 # required + readonly => required
1121 1121 assert_equal %w(due_date), issue.required_attribute_names(user)
1122 1122 end
1123 1123
1124 1124 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
1125 1125 WorkflowPermission.delete_all
1126 1126 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1127 1127 :role_id => 1, :field_name => 'due_date',
1128 1128 :rule => 'readonly')
1129 1129 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1130 1130 :role_id => 1, :field_name => 'start_date',
1131 1131 :rule => 'readonly')
1132 1132 user = User.find(2)
1133 1133 member = Member.find(1)
1134 1134 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1135 1135
1136 1136 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
1137 1137
1138 1138 member.role_ids = [1, 2]
1139 1139 member.save!
1140 1140 assert_equal [], issue.read_only_attribute_names(user.reload)
1141 1141
1142 1142 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1143 1143 :role_id => 2, :field_name => 'due_date',
1144 1144 :rule => 'readonly')
1145 1145 assert_equal %w(due_date), issue.read_only_attribute_names(user)
1146 1146 end
1147 1147
1148 1148 # A field that is not visible by role 2 and readonly by role 1 should be readonly for user with role 1 and 2
1149 1149 def test_read_only_attribute_names_should_include_custom_fields_that_combine_readonly_and_not_visible_for_roles
1150 1150 field = IssueCustomField.generate!(
1151 1151 :is_for_all => true, :trackers => Tracker.all, :visible => false, :role_ids => [1]
1152 1152 )
1153 1153 WorkflowPermission.delete_all
1154 1154 WorkflowPermission.create!(
1155 1155 :old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => field.id, :rule => 'readonly'
1156 1156 )
1157 1157 user = User.generate!
1158 1158 project = Project.find(1)
1159 1159 User.add_to_project(user, project, Role.where(:id => [1, 2]))
1160 1160
1161 1161 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1162 1162 assert_equal [field.id.to_s], issue.read_only_attribute_names(user)
1163 1163 end
1164 1164
1165 1165 def test_workflow_rules_should_ignore_roles_without_issue_permissions
1166 1166 role = Role.generate! :permissions => [:view_issues, :edit_issues]
1167 1167 ignored_role = Role.generate! :permissions => [:view_issues]
1168 1168
1169 1169 WorkflowPermission.delete_all
1170 1170 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1171 1171 :role => role, :field_name => 'due_date',
1172 1172 :rule => 'required')
1173 1173 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1174 1174 :role => role, :field_name => 'start_date',
1175 1175 :rule => 'readonly')
1176 1176 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1177 1177 :role => role, :field_name => 'done_ratio',
1178 1178 :rule => 'readonly')
1179 1179 user = User.generate!
1180 1180 User.add_to_project user, Project.find(1), [role, ignored_role]
1181 1181
1182 1182 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1183 1183
1184 1184 assert_equal %w(due_date), issue.required_attribute_names(user)
1185 1185 assert_equal %w(done_ratio start_date), issue.read_only_attribute_names(user).sort
1186 1186 end
1187 1187
1188 1188 def test_workflow_rules_should_work_for_member_with_duplicate_role
1189 1189 WorkflowPermission.delete_all
1190 1190 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1191 1191 :role_id => 1, :field_name => 'due_date',
1192 1192 :rule => 'required')
1193 1193 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1194 1194 :role_id => 1, :field_name => 'start_date',
1195 1195 :rule => 'readonly')
1196 1196
1197 1197 user = User.generate!
1198 1198 m = Member.new(:user_id => user.id, :project_id => 1)
1199 1199 m.member_roles.build(:role_id => 1)
1200 1200 m.member_roles.build(:role_id => 1)
1201 1201 m.save!
1202 1202
1203 1203 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1204 1204
1205 1205 assert_equal %w(due_date), issue.required_attribute_names(user)
1206 1206 assert_equal %w(start_date), issue.read_only_attribute_names(user)
1207 1207 end
1208 1208
1209 1209 def test_copy
1210 1210 issue = Issue.new.copy_from(1)
1211 1211 assert issue.copy?
1212 1212 assert issue.save
1213 1213 issue.reload
1214 1214 orig = Issue.find(1)
1215 1215 assert_equal orig.subject, issue.subject
1216 1216 assert_equal orig.tracker, issue.tracker
1217 1217 assert_equal "125", issue.custom_value_for(2).value
1218 1218 end
1219 1219
1220 1220 def test_copy_to_another_project_should_clear_assignee_if_not_valid
1221 1221 issue = Issue.generate!(:project_id => 1, :assigned_to_id => 2)
1222 1222 project = Project.generate!
1223 1223
1224 1224 issue = Issue.new.copy_from(1)
1225 1225 issue.project = project
1226 1226 assert_nil issue.assigned_to
1227 1227 end
1228 1228
1229 1229 def test_copy_should_copy_status
1230 1230 orig = Issue.find(8)
1231 1231 assert orig.status != orig.default_status
1232 1232
1233 1233 issue = Issue.new.copy_from(orig)
1234 1234 assert issue.save
1235 1235 issue.reload
1236 1236 assert_equal orig.status, issue.status
1237 1237 end
1238 1238
1239 1239 def test_copy_should_add_relation_with_copied_issue
1240 1240 copied = Issue.find(1)
1241 1241 issue = Issue.new.copy_from(copied)
1242 1242 assert issue.save
1243 1243 issue.reload
1244 1244
1245 1245 assert_equal 1, issue.relations.size
1246 1246 relation = issue.relations.first
1247 1247 assert_equal 'copied_to', relation.relation_type
1248 1248 assert_equal copied, relation.issue_from
1249 1249 assert_equal issue, relation.issue_to
1250 1250 end
1251 1251
1252 1252 def test_copy_should_copy_subtasks
1253 1253 issue = Issue.generate_with_descendants!
1254 1254
1255 1255 copy = issue.reload.copy
1256 1256 copy.author = User.find(7)
1257 1257 assert_difference 'Issue.count', 1+issue.descendants.count do
1258 1258 assert copy.save
1259 1259 end
1260 1260 copy.reload
1261 1261 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
1262 1262 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1263 1263 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
1264 1264 assert_equal copy.author, child_copy.author
1265 1265 end
1266 1266
1267 1267 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
1268 1268 parent = Issue.generate!
1269 1269 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1270 1270 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1271 1271
1272 1272 copy = parent.reload.copy
1273 1273 copy.parent_issue_id = parent.id
1274 1274 copy.author = User.find(7)
1275 1275 assert_difference 'Issue.count', 3 do
1276 1276 assert copy.save
1277 1277 end
1278 1278 parent.reload
1279 1279 copy.reload
1280 1280 assert_equal parent, copy.parent
1281 1281 assert_equal 3, parent.children.count
1282 1282 assert_equal 5, parent.descendants.count
1283 1283 assert_equal 2, copy.children.count
1284 1284 assert_equal 2, copy.descendants.count
1285 1285 end
1286 1286
1287 1287 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
1288 1288 parent = Issue.generate!
1289 1289 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1290 1290 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1291 1291
1292 1292 copy = parent.reload.copy
1293 1293 copy.parent_issue_id = child1.id
1294 1294 copy.author = User.find(7)
1295 1295 assert_difference 'Issue.count', 3 do
1296 1296 assert copy.save
1297 1297 end
1298 1298 parent.reload
1299 1299 child1.reload
1300 1300 copy.reload
1301 1301 assert_equal child1, copy.parent
1302 1302 assert_equal 2, parent.children.count
1303 1303 assert_equal 5, parent.descendants.count
1304 1304 assert_equal 1, child1.children.count
1305 1305 assert_equal 3, child1.descendants.count
1306 1306 assert_equal 2, copy.children.count
1307 1307 assert_equal 2, copy.descendants.count
1308 1308 end
1309 1309
1310 1310 def test_copy_should_copy_subtasks_to_target_project
1311 1311 issue = Issue.generate_with_descendants!
1312 1312
1313 1313 copy = issue.copy(:project_id => 3)
1314 1314 assert_difference 'Issue.count', 1+issue.descendants.count do
1315 1315 assert copy.save
1316 1316 end
1317 1317 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
1318 1318 end
1319 1319
1320 1320 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
1321 1321 issue = Issue.generate_with_descendants!
1322 1322
1323 1323 copy = issue.reload.copy
1324 1324 assert_difference 'Issue.count', 1+issue.descendants.count do
1325 1325 assert copy.save
1326 1326 assert copy.save
1327 1327 end
1328 1328 end
1329 1329
1330 1330 def test_copy_should_clear_closed_on
1331 1331 copied_open = Issue.find(8).copy(:status_id => 1)
1332 1332 assert copied_open.save
1333 1333 assert_nil copied_open.closed_on
1334 1334
1335 1335 copied_closed = Issue.find(8).copy
1336 1336 assert copied_closed.save
1337 1337 assert_not_nil copied_closed.closed_on
1338 1338 end
1339 1339
1340 1340 def test_should_not_call_after_project_change_on_creation
1341 1341 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1342 1342 :subject => 'Test', :author_id => 1)
1343 1343 issue.expects(:after_project_change).never
1344 1344 issue.save!
1345 1345 end
1346 1346
1347 1347 def test_should_not_call_after_project_change_on_update
1348 1348 issue = Issue.find(1)
1349 1349 issue.project = Project.find(1)
1350 1350 issue.subject = 'No project change'
1351 1351 issue.expects(:after_project_change).never
1352 1352 issue.save!
1353 1353 end
1354 1354
1355 1355 def test_should_call_after_project_change_on_project_change
1356 1356 issue = Issue.find(1)
1357 1357 issue.project = Project.find(2)
1358 1358 issue.expects(:after_project_change).once
1359 1359 issue.save!
1360 1360 end
1361 1361
1362 1362 def test_adding_journal_should_update_timestamp
1363 1363 issue = Issue.find(1)
1364 1364 updated_on_was = issue.updated_on
1365 1365
1366 1366 issue.init_journal(User.first, "Adding notes")
1367 1367 assert_difference 'Journal.count' do
1368 1368 assert issue.save
1369 1369 end
1370 1370 issue.reload
1371 1371
1372 1372 assert_not_equal updated_on_was, issue.updated_on
1373 1373 end
1374 1374
1375 1375 def test_should_close_duplicates
1376 1376 # Create 3 issues
1377 1377 issue1 = Issue.generate!
1378 1378 issue2 = Issue.generate!
1379 1379 issue3 = Issue.generate!
1380 1380
1381 1381 # 2 is a dupe of 1
1382 1382 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1383 1383 :relation_type => IssueRelation::TYPE_DUPLICATES)
1384 1384 # And 3 is a dupe of 2
1385 1385 # IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1386 1386 # :relation_type => IssueRelation::TYPE_DUPLICATES)
1387 1387 # And 3 is a dupe of 1 (circular duplicates)
1388 1388 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1389 1389 :relation_type => IssueRelation::TYPE_DUPLICATES)
1390 1390
1391 1391 assert issue1.reload.duplicates.include?(issue2)
1392 1392
1393 1393 # Closing issue 1
1394 1394 issue1.init_journal(User.first, "Closing issue1")
1395 1395 issue1.status = IssueStatus.where(:is_closed => true).first
1396 1396 assert issue1.save
1397 1397 # 2 and 3 should be also closed
1398 1398 assert issue2.reload.closed?
1399 1399 assert issue3.reload.closed?
1400 1400 end
1401 1401
1402 1402 def test_should_close_duplicates_with_private_notes
1403 1403 issue = Issue.generate!
1404 1404 duplicate = Issue.generate!
1405 1405 IssueRelation.create!(:issue_from => duplicate, :issue_to => issue,
1406 1406 :relation_type => IssueRelation::TYPE_DUPLICATES)
1407 1407 assert issue.reload.duplicates.include?(duplicate)
1408 1408
1409 1409 # Closing issue with private notes
1410 1410 issue.init_journal(User.first, "Private notes")
1411 1411 issue.private_notes = true
1412 1412 issue.status = IssueStatus.where(:is_closed => true).first
1413 1413 assert_save issue
1414 1414
1415 1415 duplicate.reload
1416 1416 assert journal = duplicate.journals.detect {|journal| journal.notes == "Private notes"}
1417 1417 assert_equal true, journal.private_notes
1418 1418 end
1419 1419
1420 1420 def test_should_not_close_duplicated_issue
1421 1421 issue1 = Issue.generate!
1422 1422 issue2 = Issue.generate!
1423 1423
1424 1424 # 2 is a dupe of 1
1425 1425 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1426 1426 :relation_type => IssueRelation::TYPE_DUPLICATES)
1427 1427 # 2 is a dup of 1 but 1 is not a duplicate of 2
1428 1428 assert !issue2.reload.duplicates.include?(issue1)
1429 1429
1430 1430 # Closing issue 2
1431 1431 issue2.init_journal(User.first, "Closing issue2")
1432 1432 issue2.status = IssueStatus.where(:is_closed => true).first
1433 1433 assert issue2.save
1434 1434 # 1 should not be also closed
1435 1435 assert !issue1.reload.closed?
1436 1436 end
1437 1437
1438 1438 def test_assignable_versions
1439 1439 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1440 1440 :status_id => 1, :fixed_version_id => 1,
1441 1441 :subject => 'New issue')
1442 1442 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1443 1443 end
1444 1444
1445 1445 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1446 1446 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1447 1447 :status_id => 1, :fixed_version_id => 1,
1448 1448 :subject => 'New issue')
1449 1449 assert !issue.save
1450 1450 assert_not_equal [], issue.errors[:fixed_version_id]
1451 1451 end
1452 1452
1453 1453 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1454 1454 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1455 1455 :status_id => 1, :fixed_version_id => 2,
1456 1456 :subject => 'New issue')
1457 1457 assert !issue.save
1458 1458 assert_not_equal [], issue.errors[:fixed_version_id]
1459 1459 end
1460 1460
1461 1461 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1462 1462 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1463 1463 :status_id => 1, :fixed_version_id => 3,
1464 1464 :subject => 'New issue')
1465 1465 assert issue.save
1466 1466 end
1467 1467
1468 1468 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1469 1469 issue = Issue.find(11)
1470 1470 assert_equal 'closed', issue.fixed_version.status
1471 1471 issue.subject = 'Subject changed'
1472 1472 assert issue.save
1473 1473 end
1474 1474
1475 1475 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1476 1476 issue = Issue.find(11)
1477 1477 issue.status_id = 1
1478 1478 assert !issue.save
1479 1479 assert_not_equal [], issue.errors[:base]
1480 1480 end
1481 1481
1482 1482 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1483 1483 issue = Issue.find(11)
1484 1484 issue.status_id = 1
1485 1485 issue.fixed_version_id = 3
1486 1486 assert issue.save
1487 1487 end
1488 1488
1489 1489 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1490 1490 issue = Issue.find(12)
1491 1491 assert_equal 'locked', issue.fixed_version.status
1492 1492 issue.status_id = 1
1493 1493 assert issue.save
1494 1494 end
1495 1495
1496 1496 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1497 1497 issue = Issue.find(2)
1498 1498 assert_equal 2, issue.fixed_version_id
1499 1499 issue.project_id = 3
1500 1500 assert_nil issue.fixed_version_id
1501 1501 issue.fixed_version_id = 2
1502 1502 assert !issue.save
1503 1503 assert_include 'Target version is not included in the list', issue.errors.full_messages
1504 1504 end
1505 1505
1506 1506 def test_should_keep_shared_version_when_changing_project
1507 1507 Version.find(2).update! :sharing => 'tree'
1508 1508
1509 1509 issue = Issue.find(2)
1510 1510 assert_equal 2, issue.fixed_version_id
1511 1511 issue.project_id = 3
1512 1512 assert_equal 2, issue.fixed_version_id
1513 1513 assert issue.save
1514 1514 end
1515 1515
1516 1516 def test_allowed_target_projects_should_include_projects_with_issue_tracking_enabled
1517 1517 assert_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1518 1518 end
1519 1519
1520 1520 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1521 1521 Project.find(2).disable_module! :issue_tracking
1522 1522 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1523 1523 end
1524 1524
1525 1525 def test_allowed_target_projects_should_not_include_projects_without_trackers
1526 1526 project = Project.generate!(:tracker_ids => [])
1527 1527 assert project.trackers.empty?
1528 1528 assert_not_include project, Issue.allowed_target_projects(User.find(1))
1529 1529 end
1530 1530
1531 1531 def test_allowed_target_trackers_with_one_role_allowed_on_all_trackers
1532 1532 user = User.generate!
1533 1533 role = Role.generate!
1534 1534 role.add_permission! :add_issues
1535 1535 role.set_permission_trackers :add_issues, :all
1536 1536 role.save!
1537 1537 User.add_to_project(user, Project.find(1), role)
1538 1538
1539 1539 assert_equal [1, 2, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
1540 1540 end
1541 1541
1542 1542 def test_allowed_target_trackers_with_one_role_allowed_on_some_trackers
1543 1543 user = User.generate!
1544 1544 role = Role.generate!
1545 1545 role.add_permission! :add_issues
1546 1546 role.set_permission_trackers :add_issues, [1, 3]
1547 1547 role.save!
1548 1548 User.add_to_project(user, Project.find(1), role)
1549 1549
1550 1550 assert_equal [1, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
1551 1551 end
1552 1552
1553 1553 def test_allowed_target_trackers_with_two_roles_allowed_on_some_trackers
1554 1554 user = User.generate!
1555 1555 role1 = Role.generate!
1556 1556 role1.add_permission! :add_issues
1557 1557 role1.set_permission_trackers :add_issues, [1]
1558 1558 role1.save!
1559 1559 role2 = Role.generate!
1560 1560 role2.add_permission! :add_issues
1561 1561 role2.set_permission_trackers :add_issues, [3]
1562 1562 role2.save!
1563 1563 User.add_to_project(user, Project.find(1), [role1, role2])
1564 1564
1565 1565 assert_equal [1, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
1566 1566 end
1567 1567
1568 1568 def test_allowed_target_trackers_with_two_roles_allowed_on_all_trackers_and_some_trackers
1569 1569 user = User.generate!
1570 1570 role1 = Role.generate!
1571 1571 role1.add_permission! :add_issues
1572 1572 role1.set_permission_trackers :add_issues, :all
1573 1573 role1.save!
1574 1574 role2 = Role.generate!
1575 1575 role2.add_permission! :add_issues
1576 1576 role2.set_permission_trackers :add_issues, [1, 3]
1577 1577 role2.save!
1578 1578 User.add_to_project(user, Project.find(1), [role1, role2])
1579 1579
1580 1580 assert_equal [1, 2, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
1581 1581 end
1582 1582
1583 1583 def test_allowed_target_trackers_should_not_consider_roles_without_add_issues_permission
1584 1584 user = User.generate!
1585 1585 role1 = Role.generate!
1586 1586 role1.remove_permission! :add_issues
1587 1587 role1.set_permission_trackers :add_issues, :all
1588 1588 role1.save!
1589 1589 role2 = Role.generate!
1590 1590 role2.add_permission! :add_issues
1591 1591 role2.set_permission_trackers :add_issues, [1, 3]
1592 1592 role2.save!
1593 1593 User.add_to_project(user, Project.find(1), [role1, role2])
1594 1594
1595 1595 assert_equal [1, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
1596 1596 end
1597 1597
1598 1598 def test_allowed_target_trackers_without_project_should_be_empty
1599 1599 issue = Issue.new
1600 1600 assert_nil issue.project
1601 1601 assert_equal [], issue.allowed_target_trackers(User.find(2)).ids
1602 1602 end
1603 1603
1604 1604 def test_allowed_target_trackers_should_include_current_tracker
1605 1605 user = User.generate!
1606 1606 role = Role.generate!
1607 1607 role.add_permission! :add_issues
1608 1608 role.set_permission_trackers :add_issues, [3]
1609 1609 role.save!
1610 1610 User.add_to_project(user, Project.find(1), role)
1611 1611
1612 1612 issue = Issue.generate!(:project => Project.find(1), :tracker => Tracker.find(1))
1613 1613 assert_equal [1, 3], issue.allowed_target_trackers(user).ids.sort
1614 1614 end
1615 1615
1616 1616 def test_move_to_another_project_with_same_category
1617 1617 issue = Issue.find(1)
1618 1618 issue.project = Project.find(2)
1619 1619 assert issue.save
1620 1620 issue.reload
1621 1621 assert_equal 2, issue.project_id
1622 1622 # Category changes
1623 1623 assert_equal 4, issue.category_id
1624 1624 # Make sure time entries were move to the target project
1625 1625 assert_equal 2, issue.time_entries.first.project_id
1626 1626 end
1627 1627
1628 1628 def test_move_to_another_project_without_same_category
1629 1629 issue = Issue.find(2)
1630 1630 issue.project = Project.find(2)
1631 1631 assert issue.save
1632 1632 issue.reload
1633 1633 assert_equal 2, issue.project_id
1634 1634 # Category cleared
1635 1635 assert_nil issue.category_id
1636 1636 end
1637 1637
1638 1638 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1639 1639 issue = Issue.find(1)
1640 1640 issue.update!(:fixed_version_id => 3)
1641 1641 issue.project = Project.find(2)
1642 1642 assert issue.save
1643 1643 issue.reload
1644 1644 assert_equal 2, issue.project_id
1645 1645 # Cleared fixed_version
1646 assert_equal nil, issue.fixed_version
1646 assert_nil issue.fixed_version
1647 1647 end
1648 1648
1649 1649 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1650 1650 issue = Issue.find(1)
1651 1651 issue.update!(:fixed_version_id => 4)
1652 1652 issue.project = Project.find(5)
1653 1653 assert issue.save
1654 1654 issue.reload
1655 1655 assert_equal 5, issue.project_id
1656 1656 # Keep fixed_version
1657 1657 assert_equal 4, issue.fixed_version_id
1658 1658 end
1659 1659
1660 1660 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1661 1661 issue = Issue.find(1)
1662 1662 issue.update!(:fixed_version_id => 3)
1663 1663 issue.project = Project.find(5)
1664 1664 assert issue.save
1665 1665 issue.reload
1666 1666 assert_equal 5, issue.project_id
1667 1667 # Cleared fixed_version
1668 assert_equal nil, issue.fixed_version
1668 assert_nil issue.fixed_version
1669 1669 end
1670 1670
1671 1671 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1672 1672 issue = Issue.find(1)
1673 1673 issue.update!(:fixed_version_id => 7)
1674 1674 issue.project = Project.find(2)
1675 1675 assert issue.save
1676 1676 issue.reload
1677 1677 assert_equal 2, issue.project_id
1678 1678 # Keep fixed_version
1679 1679 assert_equal 7, issue.fixed_version_id
1680 1680 end
1681 1681
1682 1682 def test_move_to_another_project_should_keep_parent_if_valid
1683 1683 issue = Issue.find(1)
1684 1684 issue.update! :parent_issue_id => 2
1685 1685 issue.project = Project.find(3)
1686 1686 assert issue.save
1687 1687 issue.reload
1688 1688 assert_equal 2, issue.parent_id
1689 1689 end
1690 1690
1691 1691 def test_move_to_another_project_should_clear_parent_if_not_valid
1692 1692 issue = Issue.find(1)
1693 1693 issue.update! :parent_issue_id => 2
1694 1694 issue.project = Project.find(2)
1695 1695 assert issue.save
1696 1696 issue.reload
1697 1697 assert_nil issue.parent_id
1698 1698 end
1699 1699
1700 1700 def test_move_to_another_project_with_disabled_tracker
1701 1701 issue = Issue.find(1)
1702 1702 target = Project.find(2)
1703 1703 target.tracker_ids = [3]
1704 1704 target.save
1705 1705 issue.project = target
1706 1706 assert issue.save
1707 1707 issue.reload
1708 1708 assert_equal 2, issue.project_id
1709 1709 assert_equal 3, issue.tracker_id
1710 1710 end
1711 1711
1712 1712 def test_copy_to_the_same_project
1713 1713 issue = Issue.find(1)
1714 1714 copy = issue.copy
1715 1715 assert_difference 'Issue.count' do
1716 1716 copy.save!
1717 1717 end
1718 1718 assert_kind_of Issue, copy
1719 1719 assert_equal issue.project, copy.project
1720 1720 assert_equal "125", copy.custom_value_for(2).value
1721 1721 end
1722 1722
1723 1723 def test_copy_to_another_project_and_tracker
1724 1724 issue = Issue.find(1)
1725 1725 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1726 1726 assert_difference 'Issue.count' do
1727 1727 copy.save!
1728 1728 end
1729 1729 copy.reload
1730 1730 assert_kind_of Issue, copy
1731 1731 assert_equal Project.find(3), copy.project
1732 1732 assert_equal Tracker.find(2), copy.tracker
1733 1733 # Custom field #2 is not associated with target tracker
1734 1734 assert_nil copy.custom_value_for(2)
1735 1735 end
1736 1736
1737 1737 test "#copy should not create a journal" do
1738 1738 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2}, :link => false)
1739 1739 copy.save!
1740 1740 assert_equal 0, copy.reload.journals.size
1741 1741 end
1742 1742
1743 1743 test "#copy should allow assigned_to changes" do
1744 1744 user = User.generate!
1745 1745 Member.create!(:project_id => 3, :principal => user, :role_ids => [1])
1746 1746 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => user.id)
1747 1747 assert_equal user.id, copy.assigned_to_id
1748 1748 end
1749 1749
1750 1750 test "#copy should allow status changes" do
1751 1751 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1752 1752 assert_equal 2, copy.status_id
1753 1753 end
1754 1754
1755 1755 test "#copy should allow start date changes" do
1756 1756 date = Date.today
1757 1757 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1758 1758 assert_equal date, copy.start_date
1759 1759 end
1760 1760
1761 1761 test "#copy should allow due date changes" do
1762 1762 date = Date.today
1763 1763 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1764 1764 assert_equal date, copy.due_date
1765 1765 end
1766 1766
1767 1767 test "#copy should set current user as author" do
1768 1768 User.current = User.find(9)
1769 1769 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1770 1770 assert_equal User.current, copy.author
1771 1771 end
1772 1772
1773 1773 test "#copy should create a journal with notes" do
1774 1774 date = Date.today
1775 1775 notes = "Notes added when copying"
1776 1776 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1777 1777 copy.init_journal(User.current, notes)
1778 1778 copy.save!
1779 1779
1780 1780 assert_equal 1, copy.journals.size
1781 1781 journal = copy.journals.first
1782 1782 assert_equal 0, journal.details.size
1783 1783 assert_equal notes, journal.notes
1784 1784 end
1785 1785
1786 1786 def test_valid_parent_project
1787 1787 issue = Issue.find(1)
1788 1788 issue_in_same_project = Issue.find(2)
1789 1789 issue_in_child_project = Issue.find(5)
1790 1790 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1791 1791 issue_in_other_child_project = Issue.find(6)
1792 1792 issue_in_different_tree = Issue.find(4)
1793 1793
1794 1794 with_settings :cross_project_subtasks => '' do
1795 1795 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1796 1796 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1797 1797 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1798 1798 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1799 1799 end
1800 1800
1801 1801 with_settings :cross_project_subtasks => 'system' do
1802 1802 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1803 1803 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1804 1804 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1805 1805 end
1806 1806
1807 1807 with_settings :cross_project_subtasks => 'tree' do
1808 1808 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1809 1809 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1810 1810 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1811 1811 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1812 1812
1813 1813 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1814 1814 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1815 1815 end
1816 1816
1817 1817 with_settings :cross_project_subtasks => 'descendants' do
1818 1818 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1819 1819 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1820 1820 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1821 1821 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1822 1822
1823 1823 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1824 1824 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1825 1825 end
1826 1826 end
1827 1827
1828 1828 def test_recipients_should_include_previous_assignee
1829 1829 user = User.find(3)
1830 1830 user.members.update_all ["mail_notification = ?", false]
1831 1831 user.update! :mail_notification => 'only_assigned'
1832 1832
1833 1833 issue = Issue.find(2)
1834 1834 issue.assigned_to = nil
1835 1835 assert_include user.mail, issue.recipients
1836 1836 issue.save!
1837 1837 assert !issue.recipients.include?(user.mail)
1838 1838 end
1839 1839
1840 1840 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1841 1841 issue = Issue.find(12)
1842 1842 assert issue.recipients.include?(issue.author.mail)
1843 1843 # copy the issue to a private project
1844 1844 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1845 1845 # author is not a member of project anymore
1846 1846 assert !copy.recipients.include?(copy.author.mail)
1847 1847 end
1848 1848
1849 1849 def test_recipients_should_include_the_assigned_group_members
1850 1850 group_member = User.generate!
1851 1851 group = Group.generate!
1852 1852 group.users << group_member
1853 1853
1854 1854 issue = Issue.find(12)
1855 1855 issue.assigned_to = group
1856 1856 assert issue.recipients.include?(group_member.mail)
1857 1857 end
1858 1858
1859 1859 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1860 1860 user = User.find(3)
1861 1861 issue = Issue.find(9)
1862 1862 Watcher.create!(:user => user, :watchable => issue)
1863 1863 assert issue.watched_by?(user)
1864 1864 assert !issue.watcher_recipients.include?(user.mail)
1865 1865 end
1866 1866
1867 1867 def test_issue_destroy
1868 1868 Issue.find(1).destroy
1869 1869 assert_nil Issue.find_by_id(1)
1870 1870 assert_nil TimeEntry.find_by_issue_id(1)
1871 1871 end
1872 1872
1873 1873 def test_destroy_should_delete_time_entries_custom_values
1874 1874 issue = Issue.generate!
1875 1875 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1876 1876
1877 1877 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1878 1878 assert issue.destroy
1879 1879 end
1880 1880 end
1881 1881
1882 1882 def test_destroying_a_deleted_issue_should_not_raise_an_error
1883 1883 issue = Issue.find(1)
1884 1884 Issue.find(1).destroy
1885 1885
1886 1886 assert_nothing_raised do
1887 1887 assert_no_difference 'Issue.count' do
1888 1888 issue.destroy
1889 1889 end
1890 1890 assert issue.destroyed?
1891 1891 end
1892 1892 end
1893 1893
1894 1894 def test_destroying_a_stale_issue_should_not_raise_an_error
1895 1895 issue = Issue.find(1)
1896 1896 Issue.find(1).update! :subject => "Updated"
1897 1897
1898 1898 assert_nothing_raised do
1899 1899 assert_difference 'Issue.count', -1 do
1900 1900 issue.destroy
1901 1901 end
1902 1902 assert issue.destroyed?
1903 1903 end
1904 1904 end
1905 1905
1906 1906 def test_blocked
1907 1907 blocked_issue = Issue.find(9)
1908 1908 blocking_issue = Issue.find(10)
1909 1909
1910 1910 assert blocked_issue.blocked?
1911 1911 assert !blocking_issue.blocked?
1912 1912 end
1913 1913
1914 1914 def test_blocked_issues_dont_allow_closed_statuses
1915 1915 blocked_issue = Issue.find(9)
1916 1916
1917 1917 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1918 1918 assert !allowed_statuses.empty?
1919 1919 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1920 1920 assert closed_statuses.empty?
1921 1921 end
1922 1922
1923 1923 def test_unblocked_issues_allow_closed_statuses
1924 1924 blocking_issue = Issue.find(10)
1925 1925
1926 1926 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1927 1927 assert !allowed_statuses.empty?
1928 1928 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1929 1929 assert !closed_statuses.empty?
1930 1930 end
1931 1931
1932 1932 def test_reschedule_an_issue_without_dates
1933 1933 with_settings :non_working_week_days => [] do
1934 1934 issue = Issue.new(:start_date => nil, :due_date => nil)
1935 1935 issue.reschedule_on '2012-10-09'.to_date
1936 1936 assert_equal '2012-10-09'.to_date, issue.start_date
1937 1937 assert_equal '2012-10-09'.to_date, issue.due_date
1938 1938 end
1939 1939
1940 1940 with_settings :non_working_week_days => %w(6 7) do
1941 1941 issue = Issue.new(:start_date => nil, :due_date => nil)
1942 1942 issue.reschedule_on '2012-10-09'.to_date
1943 1943 assert_equal '2012-10-09'.to_date, issue.start_date
1944 1944 assert_equal '2012-10-09'.to_date, issue.due_date
1945 1945
1946 1946 issue = Issue.new(:start_date => nil, :due_date => nil)
1947 1947 issue.reschedule_on '2012-10-13'.to_date
1948 1948 assert_equal '2012-10-15'.to_date, issue.start_date
1949 1949 assert_equal '2012-10-15'.to_date, issue.due_date
1950 1950 end
1951 1951 end
1952 1952
1953 1953 def test_reschedule_an_issue_with_start_date
1954 1954 with_settings :non_working_week_days => [] do
1955 1955 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1956 1956 issue.reschedule_on '2012-10-13'.to_date
1957 1957 assert_equal '2012-10-13'.to_date, issue.start_date
1958 1958 assert_equal '2012-10-13'.to_date, issue.due_date
1959 1959 end
1960 1960
1961 1961 with_settings :non_working_week_days => %w(6 7) do
1962 1962 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1963 1963 issue.reschedule_on '2012-10-11'.to_date
1964 1964 assert_equal '2012-10-11'.to_date, issue.start_date
1965 1965 assert_equal '2012-10-11'.to_date, issue.due_date
1966 1966
1967 1967 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1968 1968 issue.reschedule_on '2012-10-13'.to_date
1969 1969 assert_equal '2012-10-15'.to_date, issue.start_date
1970 1970 assert_equal '2012-10-15'.to_date, issue.due_date
1971 1971 end
1972 1972 end
1973 1973
1974 1974 def test_reschedule_an_issue_with_start_and_due_dates
1975 1975 with_settings :non_working_week_days => [] do
1976 1976 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1977 1977 issue.reschedule_on '2012-10-13'.to_date
1978 1978 assert_equal '2012-10-13'.to_date, issue.start_date
1979 1979 assert_equal '2012-10-19'.to_date, issue.due_date
1980 1980 end
1981 1981
1982 1982 with_settings :non_working_week_days => %w(6 7) do
1983 1983 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1984 1984 issue.reschedule_on '2012-10-11'.to_date
1985 1985 assert_equal '2012-10-11'.to_date, issue.start_date
1986 1986 assert_equal '2012-10-23'.to_date, issue.due_date
1987 1987
1988 1988 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1989 1989 issue.reschedule_on '2012-10-13'.to_date
1990 1990 assert_equal '2012-10-15'.to_date, issue.start_date
1991 1991 assert_equal '2012-10-25'.to_date, issue.due_date
1992 1992 end
1993 1993 end
1994 1994
1995 1995 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1996 1996 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1997 1997 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1998 1998 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1999 1999 :relation_type => IssueRelation::TYPE_PRECEDES)
2000 2000 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
2001 2001
2002 2002 issue1.reload
2003 2003 issue1.due_date = '2012-10-23'
2004 2004 issue1.save!
2005 2005 issue2.reload
2006 2006 assert_equal Date.parse('2012-10-24'), issue2.start_date
2007 2007 assert_equal Date.parse('2012-10-26'), issue2.due_date
2008 2008 end
2009 2009
2010 2010 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
2011 2011 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
2012 2012 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
2013 2013 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
2014 2014 :relation_type => IssueRelation::TYPE_PRECEDES)
2015 2015 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
2016 2016
2017 2017 issue1.reload
2018 2018 issue1.start_date = '2012-09-17'
2019 2019 issue1.due_date = '2012-09-18'
2020 2020 issue1.save!
2021 2021 issue2.reload
2022 2022 assert_equal Date.parse('2012-09-19'), issue2.start_date
2023 2023 assert_equal Date.parse('2012-09-21'), issue2.due_date
2024 2024 end
2025 2025
2026 2026 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
2027 2027 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
2028 2028 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
2029 2029 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
2030 2030 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
2031 2031 :relation_type => IssueRelation::TYPE_PRECEDES)
2032 2032 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
2033 2033 :relation_type => IssueRelation::TYPE_PRECEDES)
2034 2034 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
2035 2035
2036 2036 issue1.reload
2037 2037 issue1.start_date = '2012-09-17'
2038 2038 issue1.due_date = '2012-09-18'
2039 2039 issue1.save!
2040 2040 issue2.reload
2041 2041 # Issue 2 must start after Issue 3
2042 2042 assert_equal Date.parse('2012-10-03'), issue2.start_date
2043 2043 assert_equal Date.parse('2012-10-05'), issue2.due_date
2044 2044 end
2045 2045
2046 2046 def test_rescheduling_a_stale_issue_should_not_raise_an_error
2047 2047 with_settings :non_working_week_days => [] do
2048 2048 stale = Issue.find(1)
2049 2049 issue = Issue.find(1)
2050 2050 issue.subject = "Updated"
2051 2051 issue.save!
2052 2052 date = 10.days.from_now.to_date
2053 2053 assert_nothing_raised do
2054 2054 stale.reschedule_on!(date)
2055 2055 end
2056 2056 assert_equal date, stale.reload.start_date
2057 2057 end
2058 2058 end
2059 2059
2060 2060 def test_child_issue_should_consider_parent_soonest_start_on_create
2061 2061 set_language_if_valid 'en'
2062 2062 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
2063 2063 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
2064 2064 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
2065 2065 :relation_type => IssueRelation::TYPE_PRECEDES)
2066 2066 issue1.reload
2067 2067 issue2.reload
2068 2068 assert_equal Date.parse('2012-10-18'), issue2.start_date
2069 2069
2070 2070 with_settings :date_format => '%m/%d/%Y' do
2071 2071 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
2072 2072 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
2073 2073 assert !child.valid?
2074 2074 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
2075 2075 assert_equal Date.parse('2012-10-18'), child.soonest_start
2076 2076 child.start_date = '2012-10-18'
2077 2077 assert child.save
2078 2078 end
2079 2079 end
2080 2080
2081 2081 def test_setting_parent_to_a_an_issue_that_precedes_should_not_validate
2082 2082 # tests that 3 cannot have 1 as parent:
2083 2083 #
2084 2084 # 1 -> 2 -> 3
2085 2085 #
2086 2086 set_language_if_valid 'en'
2087 2087 issue1 = Issue.generate!
2088 2088 issue2 = Issue.generate!
2089 2089 issue3 = Issue.generate!
2090 2090 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
2091 2091 IssueRelation.create!(:issue_from => issue2, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES)
2092 2092 issue3.reload
2093 2093 issue3.parent_issue_id = issue1.id
2094 2094 assert !issue3.valid?
2095 2095 assert_include 'Parent task is invalid', issue3.errors.full_messages
2096 2096 end
2097 2097
2098 2098 def test_setting_parent_to_a_an_issue_that_follows_should_not_validate
2099 2099 # tests that 1 cannot have 3 as parent:
2100 2100 #
2101 2101 # 1 -> 2 -> 3
2102 2102 #
2103 2103 set_language_if_valid 'en'
2104 2104 issue1 = Issue.generate!
2105 2105 issue2 = Issue.generate!
2106 2106 issue3 = Issue.generate!
2107 2107 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
2108 2108 IssueRelation.create!(:issue_from => issue2, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES)
2109 2109 issue1.reload
2110 2110 issue1.parent_issue_id = issue3.id
2111 2111 assert !issue1.valid?
2112 2112 assert_include 'Parent task is invalid', issue1.errors.full_messages
2113 2113 end
2114 2114
2115 2115 def test_setting_parent_to_a_an_issue_that_precedes_through_hierarchy_should_not_validate
2116 2116 # tests that 4 cannot have 1 as parent:
2117 2117 # changing the due date of 4 would update the end date of 1 which would reschedule 2
2118 2118 # which would change the end date of 3 which would reschedule 4 and so on...
2119 2119 #
2120 2120 # 3 -> 4
2121 2121 # ^
2122 2122 # 1 -> 2
2123 2123 #
2124 2124 set_language_if_valid 'en'
2125 2125 issue1 = Issue.generate!
2126 2126 issue2 = Issue.generate!
2127 2127 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
2128 2128 issue3 = Issue.generate!
2129 2129 issue2.reload
2130 2130 issue2.parent_issue_id = issue3.id
2131 2131 issue2.save!
2132 2132 issue4 = Issue.generate!
2133 2133 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
2134 2134 issue4.reload
2135 2135 issue4.parent_issue_id = issue1.id
2136 2136 assert !issue4.valid?
2137 2137 assert_include 'Parent task is invalid', issue4.errors.full_messages
2138 2138 end
2139 2139
2140 2140 def test_issue_and_following_issue_should_be_able_to_be_moved_to_the_same_parent
2141 2141 set_language_if_valid 'en'
2142 2142 issue1 = Issue.generate!
2143 2143 issue2 = Issue.generate!
2144 2144 relation = IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_FOLLOWS)
2145 2145 parent = Issue.generate!
2146 2146 issue1.reload.parent_issue_id = parent.id
2147 2147 assert_save issue1
2148 2148 parent.reload
2149 2149 issue2.reload.parent_issue_id = parent.id
2150 2150 assert_save issue2
2151 2151 assert IssueRelation.exists?(relation.id)
2152 2152 end
2153 2153
2154 2154 def test_issue_and_preceding_issue_should_be_able_to_be_moved_to_the_same_parent
2155 2155 set_language_if_valid 'en'
2156 2156 issue1 = Issue.generate!
2157 2157 issue2 = Issue.generate!
2158 2158 relation = IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
2159 2159 parent = Issue.generate!
2160 2160 issue1.reload.parent_issue_id = parent.id
2161 2161 assert_save issue1
2162 2162 parent.reload
2163 2163 issue2.reload.parent_issue_id = parent.id
2164 2164 assert_save issue2
2165 2165 assert IssueRelation.exists?(relation.id)
2166 2166 end
2167 2167
2168 2168 def test_issue_and_blocked_issue_should_be_able_to_be_moved_to_the_same_parent
2169 2169 set_language_if_valid 'en'
2170 2170 issue1 = Issue.generate!
2171 2171 issue2 = Issue.generate!
2172 2172 relation = IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_BLOCKED)
2173 2173 parent = Issue.generate!
2174 2174 issue1.reload.parent_issue_id = parent.id
2175 2175 assert_save issue1
2176 2176 parent.reload
2177 2177 issue2.reload.parent_issue_id = parent.id
2178 2178 assert_save issue2
2179 2179 assert IssueRelation.exists?(relation.id)
2180 2180 end
2181 2181
2182 2182 def test_issue_and_blocking_issue_should_be_able_to_be_moved_to_the_same_parent
2183 2183 set_language_if_valid 'en'
2184 2184 issue1 = Issue.generate!
2185 2185 issue2 = Issue.generate!
2186 2186 relation = IssueRelation.create!(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_BLOCKS)
2187 2187 parent = Issue.generate!
2188 2188 issue1.reload.parent_issue_id = parent.id
2189 2189 assert_save issue1
2190 2190 parent.reload
2191 2191 issue2.reload.parent_issue_id = parent.id
2192 2192 assert_save issue2
2193 2193 assert IssueRelation.exists?(relation.id)
2194 2194 end
2195 2195
2196 2196 def test_issue_copy_should_be_able_to_be_moved_to_the_same_parent_as_copied_issue
2197 2197 issue = Issue.generate!
2198 2198 parent = Issue.generate!
2199 2199 issue.parent_issue_id = parent.id
2200 2200 issue.save!
2201 2201 issue.reload
2202 2202
2203 2203 copy = Issue.new.copy_from(issue, :link => true)
2204 2204 relation = new_record(IssueRelation) do
2205 2205 copy.save!
2206 2206 end
2207 2207
2208 2208 copy.parent_issue_id = parent.id
2209 2209 assert_save copy
2210 2210 assert IssueRelation.exists?(relation.id)
2211 2211 end
2212 2212
2213 2213 def test_overdue
2214 2214 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
2215 2215 assert !Issue.new(:due_date => Date.today).overdue?
2216 2216 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
2217 2217 assert !Issue.new(:due_date => nil).overdue?
2218 2218 assert !Issue.new(:due_date => 1.day.ago.to_date,
2219 2219 :status => IssueStatus.where(:is_closed => true).first
2220 2220 ).overdue?
2221 2221 end
2222 2222
2223 2223 test "#behind_schedule? should be false if the issue has no start_date" do
2224 2224 assert !Issue.new(:start_date => nil,
2225 2225 :due_date => 1.day.from_now.to_date,
2226 2226 :done_ratio => 0).behind_schedule?
2227 2227 end
2228 2228
2229 2229 test "#behind_schedule? should be false if the issue has no end_date" do
2230 2230 assert !Issue.new(:start_date => 1.day.from_now.to_date,
2231 2231 :due_date => nil,
2232 2232 :done_ratio => 0).behind_schedule?
2233 2233 end
2234 2234
2235 2235 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
2236 2236 assert !Issue.new(:start_date => 50.days.ago.to_date,
2237 2237 :due_date => 50.days.from_now.to_date,
2238 2238 :done_ratio => 90).behind_schedule?
2239 2239 end
2240 2240
2241 2241 test "#behind_schedule? should be true if the issue hasn't been started at all" do
2242 2242 assert Issue.new(:start_date => 1.day.ago.to_date,
2243 2243 :due_date => 1.day.from_now.to_date,
2244 2244 :done_ratio => 0).behind_schedule?
2245 2245 end
2246 2246
2247 2247 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
2248 2248 assert Issue.new(:start_date => 100.days.ago.to_date,
2249 2249 :due_date => Date.today,
2250 2250 :done_ratio => 90).behind_schedule?
2251 2251 end
2252 2252
2253 2253 test "#assignable_users should be Users" do
2254 2254 assert_kind_of User, Issue.find(1).assignable_users.first
2255 2255 end
2256 2256
2257 2257 test "#assignable_users should include the issue author" do
2258 2258 non_project_member = User.generate!
2259 2259 issue = Issue.generate!(:author => non_project_member)
2260 2260
2261 2261 assert issue.assignable_users.include?(non_project_member)
2262 2262 end
2263 2263
2264 2264 def test_assignable_users_should_not_include_anonymous_user
2265 2265 issue = Issue.generate!(:author => User.anonymous)
2266 2266
2267 2267 assert !issue.assignable_users.include?(User.anonymous)
2268 2268 end
2269 2269
2270 2270 def test_assignable_users_should_not_include_locked_user
2271 2271 user = User.generate!
2272 2272 issue = Issue.generate!(:author => user)
2273 2273 user.lock!
2274 2274
2275 2275 assert !issue.assignable_users.include?(user)
2276 2276 end
2277 2277
2278 2278 def test_assignable_users_should_include_the_current_assignee
2279 2279 user = User.generate!
2280 2280 Member.create!(:project_id => 1, :principal => user, :role_ids => [1])
2281 2281 issue = Issue.generate!(:assigned_to => user)
2282 2282 user.lock!
2283 2283
2284 2284 assert Issue.find(issue.id).assignable_users.include?(user)
2285 2285 end
2286 2286
2287 2287 test "#assignable_users should not show the issue author twice" do
2288 2288 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
2289 2289 assert_equal 2, assignable_user_ids.length
2290 2290
2291 2291 assignable_user_ids.each do |user_id|
2292 2292 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
2293 2293 "User #{user_id} appears more or less than once"
2294 2294 end
2295 2295 end
2296 2296
2297 2297 test "#assignable_users with issue_group_assignment should include groups" do
2298 2298 issue = Issue.new(:project => Project.find(2))
2299 2299
2300 2300 with_settings :issue_group_assignment => '1' do
2301 2301 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
2302 2302 assert issue.assignable_users.include?(Group.find(11))
2303 2303 end
2304 2304 end
2305 2305
2306 2306 test "#assignable_users without issue_group_assignment should not include groups" do
2307 2307 issue = Issue.new(:project => Project.find(2))
2308 2308
2309 2309 with_settings :issue_group_assignment => '0' do
2310 2310 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
2311 2311 assert !issue.assignable_users.include?(Group.find(11))
2312 2312 end
2313 2313 end
2314 2314
2315 2315 def test_assignable_users_should_not_include_builtin_groups
2316 2316 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
2317 2317 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
2318 2318 issue = Issue.new(:project => Project.find(1))
2319 2319
2320 2320 with_settings :issue_group_assignment => '1' do
2321 2321 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
2322 2322 end
2323 2323 end
2324 2324
2325 2325 def test_assignable_users_should_not_include_users_that_cannot_view_the_tracker
2326 2326 user = User.find(3)
2327 2327 role = Role.find(2)
2328 2328 role.set_permission_trackers :view_issues, [1, 3]
2329 2329 role.save!
2330 2330
2331 2331 issue1 = Issue.new(:project_id => 1, :tracker_id => 1)
2332 2332 issue2 = Issue.new(:project_id => 1, :tracker_id => 2)
2333 2333
2334 2334 assert_include user, issue1.assignable_users
2335 2335 assert_not_include user, issue2.assignable_users
2336 2336 end
2337 2337
2338 2338 def test_create_should_send_email_notification
2339 2339 ActionMailer::Base.deliveries.clear
2340 2340 issue = Issue.new(:project_id => 1, :tracker_id => 1,
2341 2341 :author_id => 3, :status_id => 1,
2342 2342 :priority => IssuePriority.all.first,
2343 2343 :subject => 'test_create', :estimated_hours => '1:30')
2344 2344 with_settings :notified_events => %w(issue_added) do
2345 2345 assert issue.save
2346 2346 assert_equal 1, ActionMailer::Base.deliveries.size
2347 2347 end
2348 2348 end
2349 2349
2350 2350 def test_create_should_send_one_email_notification_with_both_settings
2351 2351 ActionMailer::Base.deliveries.clear
2352 2352 issue = Issue.new(:project_id => 1, :tracker_id => 1,
2353 2353 :author_id => 3, :status_id => 1,
2354 2354 :priority => IssuePriority.all.first,
2355 2355 :subject => 'test_create', :estimated_hours => '1:30')
2356 2356 with_settings :notified_events => %w(issue_added issue_updated) do
2357 2357 assert issue.save
2358 2358 assert_equal 1, ActionMailer::Base.deliveries.size
2359 2359 end
2360 2360 end
2361 2361
2362 2362 def test_create_should_not_send_email_notification_with_no_setting
2363 2363 ActionMailer::Base.deliveries.clear
2364 2364 issue = Issue.new(:project_id => 1, :tracker_id => 1,
2365 2365 :author_id => 3, :status_id => 1,
2366 2366 :priority => IssuePriority.all.first,
2367 2367 :subject => 'test_create', :estimated_hours => '1:30')
2368 2368 with_settings :notified_events => [] do
2369 2369 assert issue.save
2370 2370 assert_equal 0, ActionMailer::Base.deliveries.size
2371 2371 end
2372 2372 end
2373 2373
2374 2374 def test_update_should_notify_previous_assignee
2375 2375 ActionMailer::Base.deliveries.clear
2376 2376 user = User.find(3)
2377 2377 user.members.update_all ["mail_notification = ?", false]
2378 2378 user.update! :mail_notification => 'only_assigned'
2379 2379
2380 2380 with_settings :notified_events => %w(issue_updated) do
2381 2381 issue = Issue.find(2)
2382 2382 issue.init_journal User.find(1)
2383 2383 issue.assigned_to = nil
2384 2384 issue.save!
2385 2385 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
2386 2386 end
2387 2387 end
2388 2388
2389 2389 def test_stale_issue_should_not_send_email_notification
2390 2390 ActionMailer::Base.deliveries.clear
2391 2391 issue = Issue.find(1)
2392 2392 stale = Issue.find(1)
2393 2393
2394 2394 issue.init_journal(User.find(1))
2395 2395 issue.subject = 'Subjet update'
2396 2396 with_settings :notified_events => %w(issue_updated) do
2397 2397 assert issue.save
2398 2398 assert_equal 1, ActionMailer::Base.deliveries.size
2399 2399 ActionMailer::Base.deliveries.clear
2400 2400
2401 2401 stale.init_journal(User.find(1))
2402 2402 stale.subject = 'Another subjet update'
2403 2403 assert_raise ActiveRecord::StaleObjectError do
2404 2404 stale.save
2405 2405 end
2406 2406 assert ActionMailer::Base.deliveries.empty?
2407 2407 end
2408 2408 end
2409 2409
2410 2410 def test_journalized_description
2411 2411 IssueCustomField.delete_all
2412 2412
2413 2413 i = Issue.first
2414 2414 old_description = i.description
2415 2415 new_description = "This is the new description"
2416 2416
2417 2417 i.init_journal(User.find(2))
2418 2418 i.description = new_description
2419 2419 assert_difference 'Journal.count', 1 do
2420 2420 assert_difference 'JournalDetail.count', 1 do
2421 2421 i.save!
2422 2422 end
2423 2423 end
2424 2424
2425 2425 detail = JournalDetail.order('id DESC').first
2426 2426 assert_equal i, detail.journal.journalized
2427 2427 assert_equal 'attr', detail.property
2428 2428 assert_equal 'description', detail.prop_key
2429 2429 assert_equal old_description, detail.old_value
2430 2430 assert_equal new_description, detail.value
2431 2431 end
2432 2432
2433 2433 def test_blank_descriptions_should_not_be_journalized
2434 2434 IssueCustomField.delete_all
2435 2435 Issue.where(:id => 1).update_all("description = NULL")
2436 2436
2437 2437 i = Issue.find(1)
2438 2438 i.init_journal(User.find(2))
2439 2439 i.subject = "blank description"
2440 2440 i.description = "\r\n"
2441 2441
2442 2442 assert_difference 'Journal.count', 1 do
2443 2443 assert_difference 'JournalDetail.count', 1 do
2444 2444 i.save!
2445 2445 end
2446 2446 end
2447 2447 end
2448 2448
2449 2449 def test_journalized_multi_custom_field
2450 2450 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2451 2451 :is_filter => true, :is_for_all => true,
2452 2452 :tracker_ids => [1],
2453 2453 :possible_values => ['value1', 'value2', 'value3'],
2454 2454 :multiple => true)
2455 2455
2456 2456 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2457 2457 :subject => 'Test', :author_id => 1)
2458 2458
2459 2459 assert_difference 'Journal.count' do
2460 2460 assert_difference 'JournalDetail.count' do
2461 2461 issue.init_journal(User.first)
2462 2462 issue.custom_field_values = {field.id => ['value1']}
2463 2463 issue.save!
2464 2464 end
2465 2465 assert_difference 'JournalDetail.count' do
2466 2466 issue.init_journal(User.first)
2467 2467 issue.custom_field_values = {field.id => ['value1', 'value2']}
2468 2468 issue.save!
2469 2469 end
2470 2470 assert_difference 'JournalDetail.count', 2 do
2471 2471 issue.init_journal(User.first)
2472 2472 issue.custom_field_values = {field.id => ['value3', 'value2']}
2473 2473 issue.save!
2474 2474 end
2475 2475 assert_difference 'JournalDetail.count', 2 do
2476 2476 issue.init_journal(User.first)
2477 2477 issue.custom_field_values = {field.id => nil}
2478 2478 issue.save!
2479 2479 end
2480 2480 end
2481 2481 end
2482 2482
2483 2483 def test_custom_value_cleared_on_tracker_change_should_be_journalized
2484 2484 a = IssueCustomField.generate!(:tracker_ids => [1])
2485 2485 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {a.id.to_s => "foo"})
2486 2486 assert_equal "foo", issue.custom_field_value(a)
2487 2487
2488 2488 journal = new_record(Journal) do
2489 2489 issue.init_journal(User.first)
2490 2490 issue.tracker_id = 2
2491 2491 issue.save!
2492 2492 end
2493 2493 details = journal.details.select {|d| d.property == 'cf' && d.prop_key == a.id.to_s}
2494 2494 assert_equal 1, details.size
2495 2495 assert_equal 'foo', details.first.old_value
2496 2496 assert_nil details.first.value
2497 2497 end
2498 2498
2499 2499 def test_description_eol_should_be_normalized
2500 2500 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2501 2501 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2502 2502 end
2503 2503
2504 2504 def test_saving_twice_should_not_duplicate_journal_details
2505 2505 i = Issue.first
2506 2506 i.init_journal(User.find(2), 'Some notes')
2507 2507 # initial changes
2508 2508 i.subject = 'New subject'
2509 2509 i.done_ratio = i.done_ratio + 10
2510 2510 assert_difference 'Journal.count' do
2511 2511 assert i.save
2512 2512 end
2513 2513 # 1 more change
2514 2514 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2515 2515 assert_no_difference 'Journal.count' do
2516 2516 assert_difference 'JournalDetail.count', 1 do
2517 2517 i.save
2518 2518 end
2519 2519 end
2520 2520 # no more change
2521 2521 assert_no_difference 'Journal.count' do
2522 2522 assert_no_difference 'JournalDetail.count' do
2523 2523 i.save
2524 2524 end
2525 2525 end
2526 2526 end
2527 2527
2528 2528 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2529 2529 @issue = Issue.find(1)
2530 2530 @issue_status = IssueStatus.find(1)
2531 2531 @issue_status.update!(:default_done_ratio => 50)
2532 2532 @issue2 = Issue.find(2)
2533 2533 @issue_status2 = IssueStatus.find(2)
2534 2534 @issue_status2.update!(:default_done_ratio => 0)
2535 2535
2536 2536 with_settings :issue_done_ratio => 'issue_field' do
2537 2537 assert_equal 0, @issue.done_ratio
2538 2538 assert_equal 30, @issue2.done_ratio
2539 2539 end
2540 2540
2541 2541 with_settings :issue_done_ratio => 'issue_status' do
2542 2542 assert_equal 50, @issue.done_ratio
2543 2543 assert_equal 0, @issue2.done_ratio
2544 2544 end
2545 2545 end
2546 2546
2547 2547 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2548 2548 @issue = Issue.find(1)
2549 2549 @issue_status = IssueStatus.find(1)
2550 2550 @issue_status.update!(:default_done_ratio => 50)
2551 2551 @issue2 = Issue.find(2)
2552 2552 @issue_status2 = IssueStatus.find(2)
2553 2553 @issue_status2.update!(:default_done_ratio => 0)
2554 2554
2555 2555 with_settings :issue_done_ratio => 'issue_field' do
2556 2556 @issue.update_done_ratio_from_issue_status
2557 2557 @issue2.update_done_ratio_from_issue_status
2558 2558
2559 2559 assert_equal 0, @issue.read_attribute(:done_ratio)
2560 2560 assert_equal 30, @issue2.read_attribute(:done_ratio)
2561 2561 end
2562 2562
2563 2563 with_settings :issue_done_ratio => 'issue_status' do
2564 2564 @issue.update_done_ratio_from_issue_status
2565 2565 @issue2.update_done_ratio_from_issue_status
2566 2566
2567 2567 assert_equal 50, @issue.read_attribute(:done_ratio)
2568 2568 assert_equal 0, @issue2.read_attribute(:done_ratio)
2569 2569 end
2570 2570 end
2571 2571
2572 2572 test "#by_tracker" do
2573 2573 User.current = User.anonymous
2574 2574 groups = Issue.by_tracker(Project.find(1))
2575 2575 assert_equal 3, groups.count
2576 2576 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2577 2577 end
2578 2578
2579 2579 test "#by_version" do
2580 2580 User.current = User.anonymous
2581 2581 groups = Issue.by_version(Project.find(1))
2582 2582 assert_equal 3, groups.count
2583 2583 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2584 2584 end
2585 2585
2586 2586 test "#by_priority" do
2587 2587 User.current = User.anonymous
2588 2588 groups = Issue.by_priority(Project.find(1))
2589 2589 assert_equal 4, groups.count
2590 2590 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2591 2591 end
2592 2592
2593 2593 test "#by_category" do
2594 2594 User.current = User.anonymous
2595 2595 groups = Issue.by_category(Project.find(1))
2596 2596 assert_equal 2, groups.count
2597 2597 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2598 2598 end
2599 2599
2600 2600 test "#by_assigned_to" do
2601 2601 User.current = User.anonymous
2602 2602 groups = Issue.by_assigned_to(Project.find(1))
2603 2603 assert_equal 2, groups.count
2604 2604 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2605 2605 end
2606 2606
2607 2607 test "#by_author" do
2608 2608 User.current = User.anonymous
2609 2609 groups = Issue.by_author(Project.find(1))
2610 2610 assert_equal 4, groups.count
2611 2611 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2612 2612 end
2613 2613
2614 2614 test "#by_subproject" do
2615 2615 User.current = User.anonymous
2616 2616 groups = Issue.by_subproject(Project.find(1))
2617 2617 # Private descendant not visible
2618 2618 assert_equal 1, groups.count
2619 2619 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2620 2620 end
2621 2621
2622 2622 def test_recently_updated_scope
2623 2623 #should return the last updated issue
2624 2624 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2625 2625 end
2626 2626
2627 2627 def test_on_active_projects_scope
2628 2628 assert Project.find(2).archive
2629 2629
2630 2630 before = Issue.on_active_project.length
2631 2631 # test inclusion to results
2632 2632 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2633 2633 assert_equal before + 1, Issue.on_active_project.length
2634 2634
2635 2635 # Move to an archived project
2636 2636 issue.project = Project.find(2)
2637 2637 assert issue.save
2638 2638 assert_equal before, Issue.on_active_project.length
2639 2639 end
2640 2640
2641 2641 test "Issue#recipients should include project recipients" do
2642 2642 issue = Issue.generate!
2643 2643 assert issue.project.recipients.present?
2644 2644 issue.project.recipients.each do |project_recipient|
2645 2645 assert issue.recipients.include?(project_recipient)
2646 2646 end
2647 2647 end
2648 2648
2649 2649 test "Issue#recipients should include the author if the author is active" do
2650 2650 issue = Issue.generate!(:author => User.generate!)
2651 2651 assert issue.author, "No author set for Issue"
2652 2652 assert issue.recipients.include?(issue.author.mail)
2653 2653 end
2654 2654
2655 2655 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2656 2656 user = User.generate!
2657 2657 Member.create!(:project_id => 1, :principal => user, :role_ids => [1])
2658 2658 issue = Issue.generate!(:assigned_to => user)
2659 2659 assert issue.assigned_to, "No assigned_to set for Issue"
2660 2660 assert issue.recipients.include?(issue.assigned_to.mail)
2661 2661 end
2662 2662
2663 2663 test "Issue#recipients should not include users who opt out of all email" do
2664 2664 issue = Issue.generate!(:author => User.generate!)
2665 2665 issue.author.update!(:mail_notification => :none)
2666 2666 assert !issue.recipients.include?(issue.author.mail)
2667 2667 end
2668 2668
2669 2669 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2670 2670 issue = Issue.generate!(:author => User.generate!)
2671 2671 issue.author.update!(:mail_notification => :only_assigned)
2672 2672 assert !issue.recipients.include?(issue.author.mail)
2673 2673 end
2674 2674
2675 2675 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2676 2676 user = User.generate!
2677 2677 Member.create!(:project_id => 1, :principal => user, :role_ids => [1])
2678 2678 issue = Issue.generate!(:assigned_to => user)
2679 2679 issue.assigned_to.update!(:mail_notification => :only_owner)
2680 2680 assert !issue.recipients.include?(issue.assigned_to.mail)
2681 2681 end
2682 2682
2683 2683 def test_last_journal_id_with_journals_should_return_the_journal_id
2684 2684 assert_equal 2, Issue.find(1).last_journal_id
2685 2685 end
2686 2686
2687 2687 def test_last_journal_id_without_journals_should_return_nil
2688 2688 assert_nil Issue.find(3).last_journal_id
2689 2689 end
2690 2690
2691 2691 def test_journals_after_should_return_journals_with_greater_id
2692 2692 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2693 2693 assert_equal [], Issue.find(1).journals_after('2')
2694 2694 end
2695 2695
2696 2696 def test_journals_after_with_blank_arg_should_return_all_journals
2697 2697 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2698 2698 end
2699 2699
2700 2700 def test_css_classes_should_include_tracker
2701 2701 issue = Issue.new(:tracker => Tracker.find(2))
2702 2702 classes = issue.css_classes.split(' ')
2703 2703 assert_include 'tracker-2', classes
2704 2704 end
2705 2705
2706 2706 def test_css_classes_should_include_priority
2707 2707 issue = Issue.new(:priority => IssuePriority.find(8))
2708 2708 classes = issue.css_classes.split(' ')
2709 2709 assert_include 'priority-8', classes
2710 2710 assert_include 'priority-highest', classes
2711 2711 end
2712 2712
2713 2713 def test_css_classes_should_include_user_and_group_assignment
2714 2714 project = Project.first
2715 2715 user = User.generate!
2716 2716 group = Group.generate!
2717 2717 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2718 2718 group.users << user
2719 2719 assert user.member_of?(project)
2720 2720 issue1 = Issue.generate(:assigned_to_id => group.id)
2721 2721 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2722 2722 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2723 2723 issue2 = Issue.generate(:assigned_to_id => user.id)
2724 2724 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2725 2725 assert_include 'assigned-to-me', issue2.css_classes(user)
2726 2726 end
2727 2727
2728 2728 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2729 2729 set_tmp_attachments_directory
2730 2730 issue = Issue.generate!
2731 2731 issue.save_attachments({
2732 2732 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2733 2733 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2734 2734 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2735 2735 })
2736 2736 issue.attach_saved_attachments
2737 2737
2738 2738 assert_equal 3, issue.reload.attachments.count
2739 2739 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2740 2740 end
2741 2741
2742 2742 def test_save_attachments_with_array_should_warn_about_missing_tokens
2743 2743 set_tmp_attachments_directory
2744 2744 issue = Issue.generate!
2745 2745 issue.save_attachments([
2746 2746 {'token' => 'missing'}
2747 2747 ])
2748 2748 assert !issue.save
2749 2749 assert issue.errors[:base].present?
2750 2750 assert_equal 0, issue.reload.attachments.count
2751 2751 end
2752 2752
2753 2753 def test_closed_on_should_be_nil_when_creating_an_open_issue
2754 2754 issue = Issue.generate!(:status_id => 1).reload
2755 2755 assert !issue.closed?
2756 2756 assert_nil issue.closed_on
2757 2757 end
2758 2758
2759 2759 def test_closed_on_should_be_set_when_creating_a_closed_issue
2760 2760 issue = Issue.generate!(:status_id => 5).reload
2761 2761 assert issue.closed?
2762 2762 assert_not_nil issue.closed_on
2763 2763 assert_equal issue.updated_on, issue.closed_on
2764 2764 assert_equal issue.created_on, issue.closed_on
2765 2765 end
2766 2766
2767 2767 def test_closed_on_should_be_nil_when_updating_an_open_issue
2768 2768 issue = Issue.find(1)
2769 2769 issue.subject = 'Not closed yet'
2770 2770 issue.save!
2771 2771 issue.reload
2772 2772 assert_nil issue.closed_on
2773 2773 end
2774 2774
2775 2775 def test_closed_on_should_be_set_when_closing_an_open_issue
2776 2776 issue = Issue.find(1)
2777 2777 issue.subject = 'Now closed'
2778 2778 issue.status_id = 5
2779 2779 issue.save!
2780 2780 issue.reload
2781 2781 assert_not_nil issue.closed_on
2782 2782 assert_equal issue.updated_on, issue.closed_on
2783 2783 end
2784 2784
2785 2785 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2786 2786 issue = Issue.open(false).first
2787 2787 was_closed_on = issue.closed_on
2788 2788 assert_not_nil was_closed_on
2789 2789 issue.subject = 'Updating a closed issue'
2790 2790 issue.save!
2791 2791 issue.reload
2792 2792 assert_equal was_closed_on, issue.closed_on
2793 2793 end
2794 2794
2795 2795 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2796 2796 issue = Issue.open(false).first
2797 2797 was_closed_on = issue.closed_on
2798 2798 assert_not_nil was_closed_on
2799 2799 issue.subject = 'Reopening a closed issue'
2800 2800 issue.status_id = 1
2801 2801 issue.save!
2802 2802 issue.reload
2803 2803 assert !issue.closed?
2804 2804 assert_equal was_closed_on, issue.closed_on
2805 2805 end
2806 2806
2807 2807 def test_status_was_should_return_nil_for_new_issue
2808 2808 issue = Issue.new
2809 2809 assert_nil issue.status_was
2810 2810 end
2811 2811
2812 2812 def test_status_was_should_return_status_before_change
2813 2813 issue = Issue.find(1)
2814 2814 issue.status = IssueStatus.find(2)
2815 2815 assert_equal IssueStatus.find(1), issue.status_was
2816 2816 end
2817 2817
2818 2818 def test_status_was_should_return_status_before_change_with_status_id
2819 2819 issue = Issue.find(1)
2820 2820 assert_equal IssueStatus.find(1), issue.status
2821 2821 issue.status_id = 2
2822 2822 assert_equal IssueStatus.find(1), issue.status_was
2823 2823 end
2824 2824
2825 2825 def test_status_was_should_be_reset_on_save
2826 2826 issue = Issue.find(1)
2827 2827 issue.status = IssueStatus.find(2)
2828 2828 assert_equal IssueStatus.find(1), issue.status_was
2829 2829 assert issue.save!
2830 2830 assert_equal IssueStatus.find(2), issue.status_was
2831 2831 end
2832 2832
2833 2833 def test_closing_should_return_true_when_closing_an_issue
2834 2834 issue = Issue.find(1)
2835 2835 issue.status = IssueStatus.find(2)
2836 2836 assert_equal false, issue.closing?
2837 2837 issue.status = IssueStatus.find(5)
2838 2838 assert_equal true, issue.closing?
2839 2839 end
2840 2840
2841 2841 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2842 2842 issue = Issue.find(1)
2843 2843 issue.status_id = 2
2844 2844 assert_equal false, issue.closing?
2845 2845 issue.status_id = 5
2846 2846 assert_equal true, issue.closing?
2847 2847 end
2848 2848
2849 2849 def test_closing_should_return_true_for_new_closed_issue
2850 2850 issue = Issue.new
2851 2851 assert_equal false, issue.closing?
2852 2852 issue.status = IssueStatus.find(5)
2853 2853 assert_equal true, issue.closing?
2854 2854 end
2855 2855
2856 2856 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2857 2857 issue = Issue.new
2858 2858 assert_equal false, issue.closing?
2859 2859 issue.status_id = 5
2860 2860 assert_equal true, issue.closing?
2861 2861 end
2862 2862
2863 2863 def test_closing_should_be_reset_after_save
2864 2864 issue = Issue.find(1)
2865 2865 issue.status_id = 5
2866 2866 assert_equal true, issue.closing?
2867 2867 issue.save!
2868 2868 assert_equal false, issue.closing?
2869 2869 end
2870 2870
2871 2871 def test_reopening_should_return_true_when_reopening_an_issue
2872 2872 issue = Issue.find(8)
2873 2873 issue.status = IssueStatus.find(6)
2874 2874 assert_equal false, issue.reopening?
2875 2875 issue.status = IssueStatus.find(2)
2876 2876 assert_equal true, issue.reopening?
2877 2877 end
2878 2878
2879 2879 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2880 2880 issue = Issue.find(8)
2881 2881 issue.status_id = 6
2882 2882 assert_equal false, issue.reopening?
2883 2883 issue.status_id = 2
2884 2884 assert_equal true, issue.reopening?
2885 2885 end
2886 2886
2887 2887 def test_reopening_should_return_false_for_new_open_issue
2888 2888 issue = Issue.new
2889 2889 issue.status = IssueStatus.find(1)
2890 2890 assert_equal false, issue.reopening?
2891 2891 end
2892 2892
2893 2893 def test_reopening_should_be_reset_after_save
2894 2894 issue = Issue.find(8)
2895 2895 issue.status_id = 2
2896 2896 assert_equal true, issue.reopening?
2897 2897 issue.save!
2898 2898 assert_equal false, issue.reopening?
2899 2899 end
2900 2900
2901 2901 def test_default_status_without_tracker_should_be_nil
2902 2902 issue = Issue.new
2903 2903 assert_nil issue.tracker
2904 2904 assert_nil issue.default_status
2905 2905 end
2906 2906
2907 2907 def test_default_status_should_be_tracker_default_status
2908 2908 issue = Issue.new(:tracker_id => 1)
2909 2909 assert_not_nil issue.status
2910 2910 assert_equal issue.tracker.default_status, issue.default_status
2911 2911 end
2912 2912
2913 2913 def test_initializing_with_tracker_should_set_default_status
2914 2914 issue = Issue.new(:tracker => Tracker.find(1))
2915 2915 assert_not_nil issue.status
2916 2916 assert_equal issue.default_status, issue.status
2917 2917 end
2918 2918
2919 2919 def test_initializing_with_tracker_id_should_set_default_status
2920 2920 issue = Issue.new(:tracker_id => 1)
2921 2921 assert_not_nil issue.status
2922 2922 assert_equal issue.default_status, issue.status
2923 2923 end
2924 2924
2925 2925 def test_setting_tracker_should_set_default_status
2926 2926 issue = Issue.new
2927 2927 issue.tracker = Tracker.find(1)
2928 2928 assert_not_nil issue.status
2929 2929 assert_equal issue.default_status, issue.status
2930 2930 end
2931 2931
2932 2932 def test_changing_tracker_should_set_default_status_if_status_was_default
2933 2933 WorkflowTransition.delete_all
2934 2934 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2935 2935 Tracker.find(2).update! :default_status_id => 2
2936 2936
2937 2937 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2938 2938 assert_equal IssueStatus.find(1), issue.status
2939 2939 issue.tracker = Tracker.find(2)
2940 2940 assert_equal IssueStatus.find(2), issue.status
2941 2941 end
2942 2942
2943 2943 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2944 2944 WorkflowTransition.delete_all
2945 2945 Tracker.find(2).update! :default_status_id => 2
2946 2946
2947 2947 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2948 2948 assert_equal IssueStatus.find(3), issue.status
2949 2949 issue.tracker = Tracker.find(2)
2950 2950 assert_equal IssueStatus.find(2), issue.status
2951 2951 end
2952 2952
2953 2953 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2954 2954 WorkflowTransition.delete_all
2955 2955 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2956 2956 Tracker.find(2).update! :default_status_id => 2
2957 2957
2958 2958 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2959 2959 assert_equal IssueStatus.find(3), issue.status
2960 2960 issue.tracker = Tracker.find(2)
2961 2961 assert_equal IssueStatus.find(3), issue.status
2962 2962 end
2963 2963
2964 2964 def test_assigned_to_was_with_a_group
2965 2965 group = Group.find(10)
2966 2966 Member.create!(:project_id => 1, :principal => group, :role_ids => [1])
2967 2967
2968 2968 with_settings :issue_group_assignment => '1' do
2969 2969 issue = Issue.generate!(:assigned_to => group)
2970 2970 issue.reload.assigned_to = nil
2971 2971 assert_equal group, issue.assigned_to_was
2972 2972 end
2973 2973 end
2974 2974
2975 2975 def test_issue_overdue_should_respect_user_timezone
2976 2976 user_in_europe = users(:users_001)
2977 2977 user_in_europe.pref.update! :time_zone => 'UTC'
2978 2978
2979 2979 user_in_asia = users(:users_002)
2980 2980 user_in_asia.pref.update! :time_zone => 'Hongkong'
2981 2981
2982 2982 issue = Issue.generate! :due_date => Date.parse('2016-03-20')
2983 2983
2984 2984 # server time is UTC
2985 2985 time = Time.parse '2016-03-20 20:00 UTC'
2986 2986 Time.stubs(:now).returns(time)
2987 2987 Date.stubs(:today).returns(time.to_date)
2988 2988
2989 2989 # for a user in the same time zone as the server the issue is not overdue
2990 2990 # yet
2991 2991 User.current = user_in_europe
2992 2992 assert !issue.overdue?
2993 2993
2994 2994 # at the same time, a user in East Asia looks at the issue - it's already
2995 2995 # March 21st and the issue should be marked overdue
2996 2996 User.current = user_in_asia
2997 2997 assert issue.overdue?
2998 2998
2999 2999 end
3000 3000 end
@@ -1,223 +1,223
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class JournalTest < ActiveSupport::TestCase
21 21 fixtures :projects, :issues, :issue_statuses, :journals, :journal_details,
22 22 :issue_relations, :workflows,
23 23 :users, :members, :member_roles, :roles, :enabled_modules,
24 24 :groups_users, :email_addresses,
25 25 :enumerations,
26 26 :projects_trackers, :trackers, :custom_fields
27 27
28 28 def setup
29 29 @journal = Journal.find 1
30 30 User.current = nil
31 31 end
32 32
33 33 def test_journalized_is_an_issue
34 34 issue = @journal.issue
35 35 assert_kind_of Issue, issue
36 36 assert_equal 1, issue.id
37 37 end
38 38
39 39 def test_new_status
40 40 status = @journal.new_status
41 41 assert_not_nil status
42 42 assert_kind_of IssueStatus, status
43 43 assert_equal 2, status.id
44 44 end
45 45
46 46 def test_create_should_send_email_notification
47 47 ActionMailer::Base.deliveries.clear
48 48 issue = Issue.first
49 49 user = User.first
50 50 journal = issue.init_journal(user, issue)
51 51
52 52 assert journal.save
53 53 assert_equal 1, ActionMailer::Base.deliveries.size
54 54 end
55 55
56 56 def test_should_not_save_journal_with_blank_notes_and_no_details
57 57 journal = Journal.new(:journalized => Issue.first, :user => User.first)
58 58
59 59 assert_no_difference 'Journal.count' do
60 60 assert_equal false, journal.save
61 61 end
62 62 end
63 63
64 64 def test_create_should_not_split_non_private_notes
65 65 assert_difference 'Journal.count' do
66 66 assert_no_difference 'JournalDetail.count' do
67 67 journal = Journal.generate!(:notes => 'Notes')
68 68 end
69 69 end
70 70
71 71 assert_difference 'Journal.count' do
72 72 assert_difference 'JournalDetail.count' do
73 73 journal = Journal.generate!(:notes => 'Notes', :details => [JournalDetail.new])
74 74 end
75 75 end
76 76
77 77 assert_difference 'Journal.count' do
78 78 assert_difference 'JournalDetail.count' do
79 79 journal = Journal.generate!(:notes => '', :details => [JournalDetail.new])
80 80 end
81 81 end
82 82 end
83 83
84 84 def test_create_should_split_private_notes
85 85 assert_difference 'Journal.count' do
86 86 assert_no_difference 'JournalDetail.count' do
87 87 journal = Journal.generate!(:notes => 'Notes', :private_notes => true)
88 88 journal.reload
89 89 assert_equal true, journal.private_notes
90 90 assert_equal 'Notes', journal.notes
91 91 end
92 92 end
93 93
94 94 assert_difference 'Journal.count', 2 do
95 95 assert_difference 'JournalDetail.count' do
96 96 journal = Journal.generate!(:notes => 'Notes', :private_notes => true, :details => [JournalDetail.new])
97 97 journal.reload
98 98 assert_equal true, journal.private_notes
99 99 assert_equal 'Notes', journal.notes
100 100 assert_equal 0, journal.details.size
101 101
102 102 journal_with_changes = Journal.order('id DESC').offset(1).first
103 103 assert_equal false, journal_with_changes.private_notes
104 104 assert_nil journal_with_changes.notes
105 105 assert_equal 1, journal_with_changes.details.size
106 106 assert_equal journal.created_on, journal_with_changes.created_on
107 107 end
108 108 end
109 109
110 110 assert_difference 'Journal.count' do
111 111 assert_difference 'JournalDetail.count' do
112 112 journal = Journal.generate!(:notes => '', :private_notes => true, :details => [JournalDetail.new])
113 113 journal.reload
114 114 assert_equal false, journal.private_notes
115 115 assert_equal '', journal.notes
116 116 assert_equal 1, journal.details.size
117 117 end
118 118 end
119 119 end
120 120
121 121 def test_visible_scope_for_anonymous
122 122 # Anonymous user should see issues of public projects only
123 123 journals = Journal.visible(User.anonymous).to_a
124 124 assert journals.any?
125 125 assert_nil journals.detect {|journal| !journal.issue.project.is_public?}
126 126 # Anonymous user should not see issues without permission
127 127 Role.anonymous.remove_permission!(:view_issues)
128 128 journals = Journal.visible(User.anonymous).to_a
129 129 assert journals.empty?
130 130 end
131 131
132 132 def test_visible_scope_for_user
133 133 user = User.find(9)
134 134 assert user.projects.empty?
135 135 # Non member user should see issues of public projects only
136 136 journals = Journal.visible(user).to_a
137 137 assert journals.any?
138 138 assert_nil journals.detect {|journal| !journal.issue.project.is_public?}
139 139 # Non member user should not see issues without permission
140 140 Role.non_member.remove_permission!(:view_issues)
141 141 user.reload
142 142 journals = Journal.visible(user).to_a
143 143 assert journals.empty?
144 144 # User should see issues of projects for which user has view_issues permissions only
145 145 Member.create!(:principal => user, :project_id => 1, :role_ids => [1])
146 146 user.reload
147 147 journals = Journal.visible(user).to_a
148 148 assert journals.any?
149 149 assert_nil journals.detect {|journal| journal.issue.project_id != 1}
150 150 end
151 151
152 152 def test_visible_scope_for_admin
153 153 user = User.find(1)
154 154 user.members.each(&:destroy)
155 155 assert user.projects.empty?
156 156 journals = Journal.visible(user).to_a
157 157 assert journals.any?
158 158 # Admin should see issues on private projects that admin does not belong to
159 159 assert journals.detect {|journal| !journal.issue.project.is_public?}
160 160 end
161 161
162 162 def test_preload_journals_details_custom_fields_should_set_custom_field_instance_variable
163 163 d = JournalDetail.new(:property => 'cf', :prop_key => '2')
164 164 journals = [Journal.new(:details => [d])]
165 165
166 166 d.expects(:instance_variable_set).with("@custom_field", CustomField.find(2)).once
167 167 Journal.preload_journals_details_custom_fields(journals)
168 168 end
169 169
170 170 def test_preload_journals_details_custom_fields_with_empty_set
171 171 assert_nothing_raised do
172 172 Journal.preload_journals_details_custom_fields([])
173 173 end
174 174 end
175 175
176 176 def test_details_should_normalize_dates
177 177 j = JournalDetail.create!(:old_value => Date.parse('2012-11-03'), :value => Date.parse('2013-01-02'))
178 178 j.reload
179 179 assert_equal '2012-11-03', j.old_value
180 180 assert_equal '2013-01-02', j.value
181 181 end
182 182
183 183 def test_details_should_normalize_true_values
184 184 j = JournalDetail.create!(:old_value => true, :value => true)
185 185 j.reload
186 186 assert_equal '1', j.old_value
187 187 assert_equal '1', j.value
188 188 end
189 189
190 190 def test_details_should_normalize_false_values
191 191 j = JournalDetail.create!(:old_value => false, :value => false)
192 192 j.reload
193 193 assert_equal '0', j.old_value
194 194 assert_equal '0', j.value
195 195 end
196 196
197 197 def test_custom_field_should_return_custom_field_for_cf_detail
198 198 d = JournalDetail.new(:property => 'cf', :prop_key => '2')
199 199 assert_equal CustomField.find(2), d.custom_field
200 200 end
201 201
202 202 def test_custom_field_should_return_nil_for_non_cf_detail
203 203 d = JournalDetail.new(:property => 'subject')
204 assert_equal nil, d.custom_field
204 assert_nil d.custom_field
205 205 end
206 206
207 207 def test_visible_details_should_include_relations_to_visible_issues_only
208 208 issue = Issue.generate!
209 209 visible_issue = Issue.generate!
210 210 hidden_issue = Issue.generate!(:is_private => true)
211 211
212 212 journal = Journal.new
213 213 journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'relates', :value => visible_issue.id)
214 214 journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'relates', :value => hidden_issue.id)
215 215
216 216 visible_details = journal.visible_details(User.anonymous)
217 217 assert_equal 1, visible_details.size
218 218 assert_equal visible_issue.id.to_s, visible_details.first.value.to_s
219 219
220 220 visible_details = journal.visible_details(User.find(2))
221 221 assert_equal 2, visible_details.size
222 222 end
223 223 end
@@ -1,88 +1,88
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::CodesetUtilTest < ActiveSupport::TestCase
21 21
22 22 def test_to_utf8_by_setting_from_latin1
23 23 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
24 24 s1 = "Texte encod\xc3\xa9".force_encoding("UTF-8")
25 25 s2 = "Texte encod\xe9".force_encoding("ASCII-8BIT")
26 26 s3 = s2.dup.force_encoding("UTF-8")
27 27 assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2)
28 28 assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3)
29 29 end
30 30 end
31 31
32 32 def test_to_utf8_by_setting_from_euc_jp
33 33 with_settings :repositories_encodings => 'UTF-8,EUC-JP' do
34 34 s1 = "\xe3\x83\xac\xe3\x83\x83\xe3\x83\x89\xe3\x83\x9e\xe3\x82\xa4\xe3\x83\xb3".force_encoding("UTF-8")
35 35 s2 = "\xa5\xec\xa5\xc3\xa5\xc9\xa5\xde\xa5\xa4\xa5\xf3".force_encoding("ASCII-8BIT")
36 36 s3 = s2.dup.force_encoding("UTF-8")
37 37 assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2)
38 38 assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3)
39 39 end
40 40 end
41 41
42 42 def test_to_utf8_by_setting_should_be_converted_all_latin1
43 43 with_settings :repositories_encodings => 'ISO-8859-1' do
44 44 s1 = "\xc3\x82\xc2\x80".force_encoding("UTF-8")
45 45 s2 = "\xC2\x80".force_encoding("ASCII-8BIT")
46 46 s3 = s2.dup.force_encoding("UTF-8")
47 47 assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2)
48 48 assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3)
49 49 end
50 50 end
51 51
52 52 def test_to_utf8_by_setting_blank_string
53 53 assert_equal "", Redmine::CodesetUtil.to_utf8_by_setting("")
54 assert_equal nil, Redmine::CodesetUtil.to_utf8_by_setting(nil)
54 assert_nil Redmine::CodesetUtil.to_utf8_by_setting(nil)
55 55 end
56 56
57 57 def test_to_utf8_by_setting_returns_ascii_as_utf8
58 58 s1 = "ASCII".force_encoding("UTF-8")
59 59 s2 = s1.dup.force_encoding("ISO-8859-1")
60 60 str1 = Redmine::CodesetUtil.to_utf8_by_setting(s1)
61 61 str2 = Redmine::CodesetUtil.to_utf8_by_setting(s2)
62 62 assert_equal s1, str1
63 63 assert_equal s1, str2
64 64 assert_equal "UTF-8", str1.encoding.to_s
65 65 assert_equal "UTF-8", str2.encoding.to_s
66 66 end
67 67
68 68 def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped
69 69 with_settings :repositories_encodings => '' do
70 70 # s1 = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
71 71 s1 = "Texte encod\xe9 en ISO-8859-1.".force_encoding("ASCII-8BIT")
72 72 str = Redmine::CodesetUtil.to_utf8_by_setting(s1)
73 73 assert str.valid_encoding?
74 74 assert_equal "UTF-8", str.encoding.to_s
75 75 assert_equal "Texte encod? en ISO-8859-1.", str
76 76 end
77 77 end
78 78
79 79 def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped_ja_jis
80 80 with_settings :repositories_encodings => 'ISO-2022-JP' do
81 81 s1 = "test\xb5\xfetest\xb5\xfe".force_encoding("ASCII-8BIT")
82 82 str = Redmine::CodesetUtil.to_utf8_by_setting(s1)
83 83 assert str.valid_encoding?
84 84 assert_equal "UTF-8", str.encoding.to_s
85 85 assert_equal "test??test??", str
86 86 end
87 87 end
88 88 end
@@ -1,104 +1,104
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../test_helper', __FILE__)
19 19
20 20 class PdfTest < ActiveSupport::TestCase
21 21 fixtures :users, :projects, :roles, :members, :member_roles,
22 22 :enabled_modules, :issues, :trackers, :attachments
23 23
24 24 def test_fix_text_encoding_nil
25 25 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8")
26 26 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1")
27 27 end
28 28
29 29 def test_rdm_pdf_iconv_cannot_convert_ja_cp932
30 30 utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b"
31 31 utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
32 32 utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
33 33 ["CP932", "SJIS"].each do |encoding|
34 34 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
35 35 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
36 36 txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
37 37 assert_equal "?\x91\xd4".force_encoding("ASCII-8BIT"), txt_1
38 38 assert_equal "?\x91\xd4?".force_encoding("ASCII-8BIT"), txt_2
39 39 assert_equal "??\x91\xd4?".force_encoding("ASCII-8BIT"), txt_3
40 40 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
41 41 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
42 42 assert_equal "ASCII-8BIT", txt_3.encoding.to_s
43 43 end
44 44 end
45 45
46 46 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en
47 47 str1 = "Texte encod\xe9 en ISO-8859-1".force_encoding("UTF-8")
48 48 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
49 49 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8')
50 50 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8')
51 51 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
52 52 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
53 53 assert_equal "Texte encod? en ISO-8859-1", txt_1
54 54 assert_equal "?a?b?c?d?e test", txt_2
55 55 end
56 56
57 57 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja
58 58 str1 = "Texte encod\xe9 en ISO-8859-1".force_encoding("UTF-8")
59 59 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
60 60 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
61 61 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding)
62 62 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding)
63 63 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
64 64 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
65 65 assert_equal "Texte encod? en ISO-8859-1", txt_1
66 66 assert_equal "?a?b?c?d?e test", txt_2
67 67 end
68 68
69 69 def test_attach
70 70 ["CP932", "SJIS"].each do |encoding|
71 71 set_fixtures_attachments_directory
72 72
73 73 str2 = "\x83e\x83X\x83g".force_encoding("ASCII-8BIT")
74 74
75 75 a1 = Attachment.find(17)
76 76 a2 = Attachment.find(19)
77 77 User.current = User.find(1)
78 78 assert a1.readable?
79 79 assert a1.visible?
80 80 assert a2.readable?
81 81 assert a2.visible?
82 82
83 83 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
84 84 assert_not_nil aa1
85 85 assert_equal 17, aa1.id
86 86
87 87 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
88 88 assert_not_nil aa2
89 89 assert_equal 19, aa2.id
90 90
91 91 User.current = nil
92 92 assert a1.readable?
93 93 assert (! a1.visible?)
94 94 assert a2.readable?
95 95 assert (! a2.visible?)
96 96 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
97 assert_equal nil, aa1
97 assert_nil aa1
98 98 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
99 assert_equal nil, aa2
99 assert_nil aa2
100 100
101 101 set_tmp_attachments_directory
102 102 end
103 103 end
104 104 end
@@ -1,191 +1,191
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::MenuManager::MapperTest < ActiveSupport::TestCase
21 21 test "Mapper#initialize should define a root MenuNode if menu is not present in items" do
22 22 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
23 23 node = menu_mapper.menu_items
24 24 assert_not_nil node
25 25 assert_equal :root, node.name
26 26 end
27 27
28 28 test "Mapper#initialize should use existing MenuNode if present" do
29 29 node = "foo" # just an arbitrary reference
30 30 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {:test_menu => node})
31 31 assert_equal node, menu_mapper.menu_items
32 32 end
33 33
34 34 def test_push_onto_root
35 35 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
36 36 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
37 37
38 38 menu_mapper.exists?(:test_overview)
39 39 end
40 40
41 41 def test_push_onto_parent
42 42 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
43 43 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
44 44 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview}
45 45
46 46 assert menu_mapper.exists?(:test_child)
47 47 assert_equal :test_child, menu_mapper.find(:test_child).name
48 48 end
49 49
50 50 def test_push_onto_grandparent
51 51 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
52 52 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
53 53 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview}
54 54 menu_mapper.push :test_grandchild, { :controller => 'projects', :action => 'show'}, {:parent => :test_child}
55 55
56 56 assert menu_mapper.exists?(:test_grandchild)
57 57 grandchild = menu_mapper.find(:test_grandchild)
58 58 assert_equal :test_grandchild, grandchild.name
59 59 assert_equal :test_child, grandchild.parent.name
60 60 end
61 61
62 62 def test_push_first
63 63 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
64 64 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
65 65 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
66 66 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
67 67 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
68 68 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {:first => true}
69 69
70 70 root = menu_mapper.find(:root)
71 71 assert_equal 5, root.children.size
72 72 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
73 73 assert_not_nil root.children[position]
74 74 assert_equal name, root.children[position].name
75 75 end
76 76
77 77 end
78 78
79 79 def test_push_before
80 80 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
81 81 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
82 82 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
83 83 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
84 84 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
85 85 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {:before => :test_fourth}
86 86
87 87 root = menu_mapper.find(:root)
88 88 assert_equal 5, root.children.size
89 89 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
90 90 assert_not_nil root.children[position]
91 91 assert_equal name, root.children[position].name
92 92 end
93 93
94 94 end
95 95
96 96 def test_push_after
97 97 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
98 98 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
99 99 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
100 100 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
101 101 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {}
102 102 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {:after => :test_third}
103 103
104 104 root = menu_mapper.find(:root)
105 105 assert_equal 5, root.children.size
106 106 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
107 107 assert_not_nil root.children[position]
108 108 assert_equal name, root.children[position].name
109 109 end
110 110
111 111 end
112 112
113 113 def test_push_last
114 114 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
115 115 menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {}
116 116 menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {}
117 117 menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {}
118 118 menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {:last => true}
119 119 menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {}
120 120
121 121 root = menu_mapper.find(:root)
122 122 assert_equal 5, root.children.size
123 123 {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name|
124 124 assert_not_nil root.children[position]
125 125 assert_equal name, root.children[position].name
126 126 end
127 127
128 128 end
129 129
130 130 def test_exists_for_child_node
131 131 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
132 132 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
133 133 menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview }
134 134
135 135 assert menu_mapper.exists?(:test_child)
136 136 end
137 137
138 138 def test_exists_for_invalid_node
139 139 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
140 140 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
141 141
142 142 assert !menu_mapper.exists?(:nothing)
143 143 end
144 144
145 145 def test_find
146 146 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
147 147 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
148 148
149 149 item = menu_mapper.find(:test_overview)
150 150 assert_equal :test_overview, item.name
151 151 assert_equal({:controller => 'projects', :action => 'show'}, item.url)
152 152 end
153 153
154 154 def test_find_missing
155 155 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
156 156 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
157 157
158 158 item = menu_mapper.find(:nothing)
159 assert_equal nil, item
159 assert_nil item
160 160 end
161 161
162 162 def test_delete
163 163 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
164 164 menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
165 165 assert_not_nil menu_mapper.delete(:test_overview)
166 166
167 167 assert_nil menu_mapper.find(:test_overview)
168 168 end
169 169
170 170 def test_delete_missing
171 171 menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
172 172 assert_nil menu_mapper.delete(:test_missing)
173 173 end
174 174
175 175 test 'deleting all items' do
176 176 # Exposed by deleting :last items
177 177 Redmine::MenuManager.map :test_menu do |menu|
178 178 menu.push :not_last, Redmine::Info.help_url
179 179 menu.push :administration, { :controller => 'projects', :action => 'show'}, {:last => true}
180 180 menu.push :help, Redmine::Info.help_url, :last => true
181 181 end
182 182
183 183 assert_nothing_raised do
184 184 Redmine::MenuManager.map :test_menu do |menu|
185 185 menu.delete(:administration)
186 186 menu.delete(:help)
187 187 menu.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
188 188 end
189 189 end
190 190 end
191 191 end
@@ -1,597 +1,597
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../../test_helper', __FILE__)
19 19
20 20 class GitAdapterTest < ActiveSupport::TestCase
21 21 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
22 22
23 23 FELIX_HEX = "Felix Sch\xC3\xA4fer"
24 24 CHAR_1_HEX = "\xc3\x9c"
25 25
26 26 ## Git, Mercurial and CVS path encodings are binary.
27 27 ## Subversion supports URL encoding for path.
28 28 ## Redmine Mercurial adapter and extension use URL encoding.
29 29 ## Git accepts only binary path in command line parameter.
30 30 ## So, there is no way to use binary command line parameter in JRuby.
31 31 JRUBY_SKIP = (RUBY_PLATFORM == 'java')
32 32 JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
33 33
34 34 if File.directory?(REPOSITORY_PATH)
35 35 ## Ruby uses ANSI api to fork a process on Windows.
36 36 ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
37 37 ## and these are incompatible with ASCII.
38 38 ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
39 39 ## http://code.google.com/p/msysgit/issues/detail?id=80
40 40 ## So, Latin-1 path tests fail on Japanese Windows
41 41 WINDOWS_PASS = (Redmine::Platform.mswin? &&
42 42 Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
43 43 WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
44 44
45 45 def setup
46 46 adapter_class = Redmine::Scm::Adapters::GitAdapter
47 47 assert adapter_class
48 48 assert adapter_class.client_command
49 49 assert_equal true, adapter_class.client_available
50 50 assert_equal true, adapter_class.client_version_above?([1])
51 51 assert_equal true, adapter_class.client_version_above?([1, 0])
52 52
53 53 @adapter = Redmine::Scm::Adapters::GitAdapter.new(
54 54 REPOSITORY_PATH,
55 55 nil,
56 56 nil,
57 57 nil,
58 58 'ISO-8859-1'
59 59 )
60 60 assert @adapter
61 61 @char_1 = CHAR_1_HEX.dup.force_encoding('UTF-8')
62 62 @str_felix_hex = FELIX_HEX.dup.force_encoding('ASCII-8BIT')
63 63 end
64 64
65 65 def test_scm_version
66 66 to_test = { "git version 1.7.3.4\n" => [1,7,3,4],
67 67 "1.6.1\n1.7\n1.8" => [1,6,1],
68 68 "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]}
69 69 to_test.each do |s, v|
70 70 test_scm_version_for(s, v)
71 71 end
72 72 end
73 73
74 74 def test_branches
75 75 brs = []
76 76 @adapter.branches.each do |b|
77 77 brs << b
78 78 end
79 79 assert_equal 6, brs.length
80 80 br_issue_8857 = brs[0]
81 81 assert_equal 'issue-8857', br_issue_8857.to_s
82 82 assert_equal '2a682156a3b6e77a8bf9cd4590e8db757f3c6c78', br_issue_8857.revision
83 83 assert_equal br_issue_8857.scmid, br_issue_8857.revision
84 84 assert_equal false, br_issue_8857.is_default
85 85 br_latin_1_path = brs[1]
86 86 assert_equal 'latin-1-path-encoding', br_latin_1_path.to_s
87 87 assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', br_latin_1_path.revision
88 88 assert_equal br_latin_1_path.scmid, br_latin_1_path.revision
89 89 assert_equal false, br_latin_1_path.is_default
90 90 br_master = brs[2]
91 91 assert_equal 'master', br_master.to_s
92 92 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', br_master.revision
93 93 assert_equal br_master.scmid, br_master.revision
94 94 assert_equal false, br_master.is_default
95 95 br_master_20120212 = brs[3]
96 96 assert_equal 'master-20120212', br_master_20120212.to_s
97 97 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', br_master_20120212.revision
98 98 assert_equal br_master_20120212.scmid, br_master_20120212.revision
99 99 assert_equal true, br_master_20120212.is_default
100 100 br_latin_1 = brs[-2]
101 101 assert_equal 'test-latin-1', br_latin_1.to_s
102 102 assert_equal '67e7792ce20ccae2e4bb73eed09bb397819c8834', br_latin_1.revision
103 103 assert_equal br_latin_1.scmid, br_latin_1.revision
104 104 assert_equal false, br_latin_1.is_default
105 105 br_test = brs[-1]
106 106 assert_equal 'test_branch', br_test.to_s
107 107 assert_equal 'fba357b886984ee71185ad2065e65fc0417d9b92', br_test.revision
108 108 assert_equal br_test.scmid, br_test.revision
109 109 assert_equal false, br_test.is_default
110 110 end
111 111
112 112 def test_default_branch
113 113 assert_equal 'master-20120212', @adapter.default_branch
114 114 end
115 115
116 116 def test_tags
117 117 assert_equal [
118 118 "tag00.lightweight",
119 119 "tag01.annotated",
120 120 ], @adapter.tags
121 121 end
122 122
123 123 def test_revisions_master_all
124 124 revs1 = []
125 125 @adapter.revisions('', nil, "master",{}) do |rev|
126 126 revs1 << rev
127 127 end
128 128 assert_equal 15, revs1.length
129 129 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[ 0].identifier
130 130 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[-1].identifier
131 131
132 132 revs2 = []
133 133 @adapter.revisions('', nil, "master",
134 134 {:reverse => true}) do |rev|
135 135 revs2 << rev
136 136 end
137 137 assert_equal 15, revs2.length
138 138 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs2[-1].identifier
139 139 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs2[ 0].identifier
140 140 end
141 141
142 142 def test_revisions_master_merged_rev
143 143 revs1 = []
144 144 @adapter.revisions('',
145 145 "713f4944648826f558cf548222f813dabe7cbb04",
146 146 "master",
147 147 {:reverse => true}) do |rev|
148 148 revs1 << rev
149 149 end
150 150 assert_equal 8, revs1.length
151 151 assert_equal 'fba357b886984ee71185ad2065e65fc0417d9b92', revs1[ 0].identifier
152 152 assert_equal '7e61ac704deecde634b51e59daa8110435dcb3da', revs1[ 1].identifier
153 153 # 4a07fe31b is not a child of 713f49446
154 154 assert_equal '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', revs1[ 2].identifier
155 155 # Merged revision
156 156 assert_equal '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', revs1[ 3].identifier
157 157 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[-1].identifier
158 158
159 159 revs2 = []
160 160 @adapter.revisions('',
161 161 "fba357b886984ee71185ad2065e65fc0417d9b92",
162 162 "master",
163 163 {:reverse => true}) do |rev|
164 164 revs2 << rev
165 165 end
166 166 assert_equal 7, revs2.length
167 167 assert_equal '7e61ac704deecde634b51e59daa8110435dcb3da', revs2[ 0].identifier
168 168 # 4a07fe31b is not a child of fba357b8869
169 169 assert_equal '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', revs2[ 1].identifier
170 170 # Merged revision
171 171 assert_equal '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', revs2[ 2].identifier
172 172 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs2[-1].identifier
173 173 end
174 174
175 175 def test_revisions_branch_latin_1_path_encoding_all
176 176 revs1 = []
177 177 @adapter.revisions('', nil, "latin-1-path-encoding",{}) do |rev|
178 178 revs1 << rev
179 179 end
180 180 assert_equal 8, revs1.length
181 181 assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[ 0].identifier
182 182 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[-1].identifier
183 183
184 184 revs2 = []
185 185 @adapter.revisions('', nil, "latin-1-path-encoding",
186 186 {:reverse => true}) do |rev|
187 187 revs2 << rev
188 188 end
189 189 assert_equal 8, revs2.length
190 190 assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs2[-1].identifier
191 191 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs2[ 0].identifier
192 192 end
193 193
194 194 def test_revisions_branch_latin_1_path_encoding_with_rev
195 195 revs1 = []
196 196 @adapter.revisions('',
197 197 '7234cb2750b63f47bff735edc50a1c0a433c2518',
198 198 "latin-1-path-encoding",
199 199 {:reverse => true}) do |rev|
200 200 revs1 << rev
201 201 end
202 202 assert_equal 7, revs1.length
203 203 assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', revs1[ 0].identifier
204 204 assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[-1].identifier
205 205
206 206 revs2 = []
207 207 @adapter.revisions('',
208 208 '57ca437c0acbbcb749821fdf3726a1367056d364',
209 209 "latin-1-path-encoding",
210 210 {:reverse => true}) do |rev|
211 211 revs2 << rev
212 212 end
213 213 assert_equal 3, revs2.length
214 214 assert_equal '4fc55c43bf3d3dc2efb66145365ddc17639ce81e', revs2[ 0].identifier
215 215 assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs2[-1].identifier
216 216 end
217 217
218 218 def test_revisions_invalid_rev
219 219 assert_equal [], @adapter.revisions('', '1234abcd', "master")
220 220 assert_raise Redmine::Scm::Adapters::CommandFailed do
221 221 revs1 = []
222 222 @adapter.revisions('',
223 223 '1234abcd',
224 224 "master",
225 225 {:reverse => true}) do |rev|
226 226 revs1 << rev
227 227 end
228 228 end
229 229 end
230 230
231 231 def test_revisions_includes_master_two_revs
232 232 revs1 = []
233 233 @adapter.revisions('', nil, nil,
234 234 {:reverse => true,
235 235 :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'],
236 236 :excludes => ['4f26664364207fa8b1af9f8722647ab2d4ac5d43']}) do |rev|
237 237 revs1 << rev
238 238 end
239 239 assert_equal 2, revs1.length
240 240 assert_equal 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b', revs1[ 0].identifier
241 241 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[-1].identifier
242 242 end
243 243
244 244 def test_revisions_includes_master_two_revs_from_origin
245 245 revs1 = []
246 246 @adapter.revisions('', nil, nil,
247 247 {:reverse => true,
248 248 :includes => ['899a15dba03a3b350b89c3f537e4bbe02a03cdc9'],
249 249 :excludes => []}) do |rev|
250 250 revs1 << rev
251 251 end
252 252 assert_equal 2, revs1.length
253 253 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[ 0].identifier
254 254 assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', revs1[ 1].identifier
255 255 end
256 256
257 257 def test_revisions_includes_merged_revs
258 258 revs1 = []
259 259 @adapter.revisions('', nil, nil,
260 260 {:reverse => true,
261 261 :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'],
262 262 :excludes => ['fba357b886984ee71185ad2065e65fc0417d9b92']}) do |rev|
263 263 revs1 << rev
264 264 end
265 265 assert_equal 7, revs1.length
266 266 assert_equal '7e61ac704deecde634b51e59daa8110435dcb3da', revs1[ 0].identifier
267 267 assert_equal '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', revs1[ 1].identifier
268 268 assert_equal '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', revs1[ 2].identifier
269 269 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[-1].identifier
270 270 end
271 271
272 272 def test_revisions_includes_two_heads
273 273 revs1 = []
274 274 @adapter.revisions('', nil, nil,
275 275 {:reverse => true,
276 276 :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c',
277 277 '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127'],
278 278 :excludes => ['4f26664364207fa8b1af9f8722647ab2d4ac5d43',
279 279 '4fc55c43bf3d3dc2efb66145365ddc17639ce81e']}) do |rev|
280 280 revs1 << rev
281 281 end
282 282 assert_equal 4, revs1.length
283 283 assert_equal 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b', revs1[ 0].identifier
284 284 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[ 1].identifier
285 285 assert_equal '64f1f3e89ad1cb57976ff0ad99a107012ba3481d', revs1[-2].identifier
286 286 assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[-1].identifier
287 287 end
288 288
289 289 def test_revisions_disjointed_histories_revisions
290 290 revs1 = []
291 291 @adapter.revisions('', nil, nil,
292 292 {:reverse => true,
293 293 :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c',
294 294 '92397af84d22f27389c822848ecd5b463c181583'],
295 295 :excludes => ['95488a44bc25f7d1f97d775a31359539ff333a63',
296 296 '4f26664364207fa8b1af9f8722647ab2d4ac5d43'] }) do |rev|
297 297 revs1 << rev
298 298 end
299 299 assert_equal 4, revs1.length
300 300 assert_equal 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b', revs1[ 0].identifier
301 301 assert_equal '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', revs1[ 1].identifier
302 302 assert_equal 'bc201c95999c4f10d018b0aa03b541cd6a2ff0ee', revs1[-2].identifier
303 303 assert_equal '92397af84d22f27389c822848ecd5b463c181583', revs1[-1].identifier
304 304 end
305 305
306 306 def test_revisions_invalid_rev_excludes
307 307 assert_equal [],
308 308 @adapter.revisions('', nil, nil,
309 309 {:reverse => true,
310 310 :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'],
311 311 :excludes => ['0123abcd4567']})
312 312 assert_raise Redmine::Scm::Adapters::CommandFailed do
313 313 revs1 = []
314 314 @adapter.revisions('', nil, nil,
315 315 {:reverse => true,
316 316 :includes => ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c'],
317 317 :excludes => ['0123abcd4567']}) do |rev|
318 318 revs1 << rev
319 319 end
320 320 end
321 321 end
322 322
323 323 def test_getting_revisions_with_spaces_in_filename
324 324 assert_equal 1, @adapter.revisions("filemane with spaces.txt",
325 325 nil, "master").length
326 326 end
327 327
328 328 def test_parents
329 329 revs1 = []
330 330 @adapter.revisions('',
331 331 nil,
332 332 "master",
333 333 {:reverse => true}) do |rev|
334 334 revs1 << rev
335 335 end
336 336 assert_equal 15, revs1.length
337 337 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
338 338 revs1[0].identifier
339 assert_equal nil, revs1[0].parents
339 assert_nil revs1[0].parents
340 340 assert_equal "899a15dba03a3b350b89c3f537e4bbe02a03cdc9",
341 341 revs1[1].identifier
342 342 assert_equal 1, revs1[1].parents.length
343 343 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
344 344 revs1[1].parents[0]
345 345 assert_equal "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
346 346 revs1[10].identifier
347 347 assert_equal 2, revs1[10].parents.length
348 348 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8",
349 349 revs1[10].parents[0]
350 350 assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da",
351 351 revs1[10].parents[1]
352 352 end
353 353
354 354 def test_getting_revisions_with_leading_and_trailing_spaces_in_filename
355 355 assert_equal " filename with a leading space.txt ",
356 356 @adapter.revisions(" filename with a leading space.txt ",
357 357 nil, "master")[0].paths[0][:path]
358 358 end
359 359
360 360 def test_getting_entries_with_leading_and_trailing_spaces_in_filename
361 361 assert_equal " filename with a leading space.txt ",
362 362 @adapter.entries('',
363 363 '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c')[3].name
364 364 end
365 365
366 366 def test_annotate
367 367 annotate = @adapter.annotate('sources/watchers_controller.rb')
368 368 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
369 369 assert_equal 41, annotate.lines.size
370 370 assert_equal "# This program is free software; you can redistribute it and/or",
371 371 annotate.lines[4].strip
372 372 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
373 373 annotate.revisions[4].identifier
374 374 assert_equal "jsmith", annotate.revisions[4].author
375 375 end
376 376
377 377 def test_annotate_moved_file
378 378 annotate = @adapter.annotate('renamed_test.txt')
379 379 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
380 380 assert_equal 2, annotate.lines.size
381 381 end
382 382
383 383 def test_last_rev
384 384 last_rev = @adapter.lastrev("README",
385 385 "4f26664364207fa8b1af9f8722647ab2d4ac5d43")
386 386 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.scmid
387 387 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.identifier
388 388 assert_equal "Adam Soltys <asoltys@gmail.com>", last_rev.author
389 389 assert_equal Time.gm(2009, 6, 24, 5, 27, 38), last_rev.time
390 390 end
391 391
392 392 def test_last_rev_with_spaces_in_filename
393 393 last_rev = @adapter.lastrev("filemane with spaces.txt",
394 394 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b")
395 395 last_rev_author = last_rev.author
396 396 assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid
397 397 assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier
398 398 assert_equal "#{@str_felix_hex} <felix@fachschaften.org>",
399 399 last_rev.author
400 400 assert_equal Time.gm(2010, 9, 18, 19, 59, 46), last_rev.time
401 401 end
402 402
403 403 def test_latin_1_path
404 404 if WINDOWS_PASS
405 405 puts WINDOWS_SKIP_STR
406 406 elsif JRUBY_SKIP
407 407 puts JRUBY_SKIP_STR
408 408 else
409 409 p2 = "latin-1-dir/test-#{@char_1}-2.txt"
410 410 ['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1|
411 411 assert @adapter.diff(p2, r1)
412 412 assert @adapter.cat(p2, r1)
413 413 assert_equal 1, @adapter.annotate(p2, r1).lines.length
414 414 ['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2|
415 415 assert @adapter.diff(p2, r1, r2)
416 416 end
417 417 end
418 418 end
419 419 end
420 420
421 421 def test_latin_1_user_annotate
422 422 ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1|
423 423 annotate = @adapter.annotate(" filename with a leading space.txt ", r1)
424 424 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
425 425 assert_equal 1, annotate.lines.size
426 426 assert_equal "And this is a file with a leading and trailing space...",
427 427 annotate.lines[0].strip
428 428 assert_equal "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
429 429 annotate.revisions[0].identifier
430 430 assert_equal @str_felix_hex, annotate.revisions[0].author
431 431 end
432 432 end
433 433
434 434 def test_entries_tag
435 435 entries1 = @adapter.entries(nil, 'tag01.annotated',
436 436 options = {:report_last_commit => true})
437 437 assert entries1
438 438 assert_equal 3, entries1.size
439 439 assert_equal 'sources', entries1[1].name
440 440 assert_equal 'sources', entries1[1].path
441 441 assert_equal 'dir', entries1[1].kind
442 442 readme = entries1[2]
443 443 assert_equal 'README', readme.name
444 444 assert_equal 'README', readme.path
445 445 assert_equal 'file', readme.kind
446 446 assert_equal 27, readme.size
447 447 assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', readme.lastrev.identifier
448 448 assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time
449 449 end
450 450
451 451 def test_entries_branch
452 452 entries1 = @adapter.entries(nil, 'test_branch',
453 453 options = {:report_last_commit => true})
454 454 assert entries1
455 455 assert_equal 4, entries1.size
456 456 assert_equal 'sources', entries1[1].name
457 457 assert_equal 'sources', entries1[1].path
458 458 assert_equal 'dir', entries1[1].kind
459 459 readme = entries1[2]
460 460 assert_equal 'README', readme.name
461 461 assert_equal 'README', readme.path
462 462 assert_equal 'file', readme.kind
463 463 assert_equal 159, readme.size
464 464 assert_equal '713f4944648826f558cf548222f813dabe7cbb04', readme.lastrev.identifier
465 465 assert_equal Time.gm(2009, 6, 19, 4, 37, 23), readme.lastrev.time
466 466 end
467 467
468 468 def test_entries_wrong_path_encoding
469 469 adpt = Redmine::Scm::Adapters::GitAdapter.new(
470 470 REPOSITORY_PATH,
471 471 nil,
472 472 nil,
473 473 nil,
474 474 'EUC-JP'
475 475 )
476 476 entries1 = adpt.entries('latin-1-dir', '64f1f3e8')
477 477 assert entries1
478 478 assert_equal 3, entries1.size
479 479 f1 = entries1[1]
480 assert_equal nil, f1.name
481 assert_equal nil, f1.path
480 assert_nil f1.name
481 assert_nil f1.path
482 482 assert_equal 'file', f1.kind
483 483 end
484 484
485 485 def test_entries_latin_1_files
486 486 entries1 = @adapter.entries('latin-1-dir', '64f1f3e8')
487 487 assert entries1
488 488 assert_equal 3, entries1.size
489 489 f1 = entries1[1]
490 490 assert_equal "test-#{@char_1}-2.txt", f1.name
491 491 assert_equal "latin-1-dir/test-#{@char_1}-2.txt", f1.path
492 492 assert_equal 'file', f1.kind
493 493 end
494 494
495 495 def test_entries_latin_1_dir
496 496 if WINDOWS_PASS
497 497 puts WINDOWS_SKIP_STR
498 498 elsif JRUBY_SKIP
499 499 puts JRUBY_SKIP_STR
500 500 else
501 501 entries1 = @adapter.entries("latin-1-dir/test-#{@char_1}-subdir",
502 502 '1ca7f5ed')
503 503 assert entries1
504 504 assert_equal 3, entries1.size
505 505 f1 = entries1[1]
506 506 assert_equal "test-#{@char_1}-2.txt", f1.name
507 507 assert_equal "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-2.txt", f1.path
508 508 assert_equal 'file', f1.kind
509 509 end
510 510 end
511 511
512 512 def test_entry
513 513 entry = @adapter.entry()
514 514 assert_equal "", entry.path
515 515 assert_equal "dir", entry.kind
516 516 entry = @adapter.entry('')
517 517 assert_equal "", entry.path
518 518 assert_equal "dir", entry.kind
519 519 assert_nil @adapter.entry('invalid')
520 520 assert_nil @adapter.entry('/invalid')
521 521 assert_nil @adapter.entry('/invalid/')
522 522 assert_nil @adapter.entry('invalid/invalid')
523 523 assert_nil @adapter.entry('invalid/invalid/')
524 524 assert_nil @adapter.entry('/invalid/invalid')
525 525 assert_nil @adapter.entry('/invalid/invalid/')
526 526 ["README", "/README"].each do |path|
527 527 entry = @adapter.entry(path, '7234cb2750b63f')
528 528 assert_equal "README", entry.path
529 529 assert_equal "file", entry.kind
530 530 end
531 531 ["sources", "/sources", "/sources/"].each do |path|
532 532 entry = @adapter.entry(path, '7234cb2750b63f')
533 533 assert_equal "sources", entry.path
534 534 assert_equal "dir", entry.kind
535 535 end
536 536 ["sources/watchers_controller.rb", "/sources/watchers_controller.rb"].each do |path|
537 537 entry = @adapter.entry(path, '7234cb2750b63f')
538 538 assert_equal "sources/watchers_controller.rb", entry.path
539 539 assert_equal "file", entry.kind
540 540 end
541 541 end
542 542
543 543 def test_path_encoding_default_utf8
544 544 adpt1 = Redmine::Scm::Adapters::GitAdapter.new(
545 545 REPOSITORY_PATH
546 546 )
547 547 assert_equal "UTF-8", adpt1.path_encoding
548 548 adpt2 = Redmine::Scm::Adapters::GitAdapter.new(
549 549 REPOSITORY_PATH,
550 550 nil,
551 551 nil,
552 552 nil,
553 553 ""
554 554 )
555 555 assert_equal "UTF-8", adpt2.path_encoding
556 556 end
557 557
558 558 def test_cat_path_invalid
559 559 assert_nil @adapter.cat('invalid')
560 560 end
561 561
562 562 def test_cat_revision_invalid
563 563 assert @adapter.cat('README')
564 564 assert_nil @adapter.cat('README', '1234abcd5678')
565 565 end
566 566
567 567 def test_diff_path_invalid
568 568 assert_equal [], @adapter.diff('invalid', '713f4944648826f5')
569 569 end
570 570
571 571 def test_diff_revision_invalid
572 572 assert_nil @adapter.diff(nil, '1234abcd5678')
573 573 assert_nil @adapter.diff(nil, '713f4944648826f5', '1234abcd5678')
574 574 assert_nil @adapter.diff(nil, '1234abcd5678', '713f4944648826f5')
575 575 end
576 576
577 577 def test_annotate_path_invalid
578 578 assert_nil @adapter.annotate('invalid')
579 579 end
580 580
581 581 def test_annotate_revision_invalid
582 582 assert @adapter.annotate('README')
583 583 assert_nil @adapter.annotate('README', '1234abcd5678')
584 584 end
585 585
586 586 private
587 587
588 588 def test_scm_version_for(scm_command_version, version)
589 589 @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version)
590 590 assert_equal version, @adapter.class.scm_command_version
591 591 end
592 592
593 593 else
594 594 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
595 595 def test_fake; assert true end
596 596 end
597 597 end
@@ -1,1003 +1,1003
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class ProjectTest < ActiveSupport::TestCase
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :journals, :journal_details,
23 23 :enumerations, :users, :issue_categories,
24 24 :projects_trackers,
25 25 :custom_fields,
26 26 :custom_fields_projects,
27 27 :custom_fields_trackers,
28 28 :custom_values,
29 29 :roles,
30 30 :member_roles,
31 31 :members,
32 32 :enabled_modules,
33 33 :versions,
34 34 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
35 35 :groups_users,
36 36 :boards, :messages,
37 37 :repositories,
38 38 :news, :comments,
39 39 :documents,
40 40 :workflows
41 41
42 42 def setup
43 43 @ecookbook = Project.find(1)
44 44 @ecookbook_sub1 = Project.find(3)
45 45 set_tmp_attachments_directory
46 46 User.current = nil
47 47 end
48 48
49 49 def test_truth
50 50 assert_kind_of Project, @ecookbook
51 51 assert_equal "eCookbook", @ecookbook.name
52 52 end
53 53
54 54 def test_default_attributes
55 55 with_settings :default_projects_public => '1' do
56 56 assert_equal true, Project.new.is_public
57 57 assert_equal false, Project.new(:is_public => false).is_public
58 58 end
59 59
60 60 with_settings :default_projects_public => '0' do
61 61 assert_equal false, Project.new.is_public
62 62 assert_equal true, Project.new(:is_public => true).is_public
63 63 end
64 64
65 65 with_settings :sequential_project_identifiers => '1' do
66 66 assert !Project.new.identifier.blank?
67 67 assert Project.new(:identifier => '').identifier.blank?
68 68 end
69 69
70 70 with_settings :sequential_project_identifiers => '0' do
71 71 assert Project.new.identifier.blank?
72 72 assert !Project.new(:identifier => 'test').blank?
73 73 end
74 74
75 75 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
76 76 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
77 77 end
78 78 end
79 79
80 80 def test_default_trackers_should_match_default_tracker_ids_setting
81 81 with_settings :default_projects_tracker_ids => ['1', '3'] do
82 82 assert_equal Tracker.find(1, 3).sort, Project.new.trackers.sort
83 83 end
84 84 end
85 85
86 86 def test_default_trackers_should_be_all_trackers_with_blank_setting
87 87 with_settings :default_projects_tracker_ids => nil do
88 88 assert_equal Tracker.all.sort, Project.new.trackers.sort
89 89 end
90 90 end
91 91
92 92 def test_default_trackers_should_be_empty_with_empty_setting
93 93 with_settings :default_projects_tracker_ids => [] do
94 94 assert_equal [], Project.new.trackers
95 95 end
96 96 end
97 97
98 98 def test_default_trackers_should_not_replace_initialized_trackers
99 99 with_settings :default_projects_tracker_ids => ['1', '3'] do
100 100 assert_equal Tracker.find(1, 2).sort, Project.new(:tracker_ids => [1, 2]).trackers.sort
101 101 end
102 102 end
103 103
104 104 def test_update
105 105 assert_equal "eCookbook", @ecookbook.name
106 106 @ecookbook.name = "eCook"
107 107 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
108 108 @ecookbook.reload
109 109 assert_equal "eCook", @ecookbook.name
110 110 end
111 111
112 112 def test_validate_identifier
113 113 to_test = {"abc" => true,
114 114 "ab12" => true,
115 115 "ab-12" => true,
116 116 "ab_12" => true,
117 117 "12" => false,
118 118 "new" => false}
119 119
120 120 to_test.each do |identifier, valid|
121 121 p = Project.new
122 122 p.identifier = identifier
123 123 p.valid?
124 124 if valid
125 125 assert p.errors['identifier'].blank?, "identifier #{identifier} was not valid"
126 126 else
127 127 assert p.errors['identifier'].present?, "identifier #{identifier} was valid"
128 128 end
129 129 end
130 130 end
131 131
132 132 def test_identifier_should_not_be_frozen_for_a_new_project
133 133 assert_equal false, Project.new.identifier_frozen?
134 134 end
135 135
136 136 def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier
137 137 Project.where(:id => 1).update_all(["identifier = ''"])
138 138 assert_equal false, Project.find(1).identifier_frozen?
139 139 end
140 140
141 141 def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier
142 142 assert_equal true, Project.find(1).identifier_frozen?
143 143 end
144 144
145 145 def test_to_param_should_be_nil_for_new_records
146 146 project = Project.new
147 147 project.identifier = "foo"
148 148 assert_nil project.to_param
149 149 end
150 150
151 151 def test_members_should_be_active_users
152 152 Project.all.each do |project|
153 153 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
154 154 end
155 155 end
156 156
157 157 def test_users_should_be_active_users
158 158 Project.all.each do |project|
159 159 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
160 160 end
161 161 end
162 162
163 163 def test_open_scope_on_issues_association
164 164 assert_kind_of Issue, Project.find(1).issues.open.first
165 165 end
166 166
167 167 def test_archive
168 168 user = @ecookbook.members.first.user
169 169 @ecookbook.archive
170 170 @ecookbook.reload
171 171
172 172 assert !@ecookbook.active?
173 173 assert @ecookbook.archived?
174 174 assert !user.projects.include?(@ecookbook)
175 175 # Subproject are also archived
176 176 assert !@ecookbook.children.empty?
177 177 assert @ecookbook.descendants.active.empty?
178 178 end
179 179
180 180 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
181 181 # Assign an issue of a project to a version of a child project
182 182 Issue.find(4).update_attribute :fixed_version_id, 4
183 183
184 184 assert_no_difference "Project.where(:status => Project::STATUS_ARCHIVED).count" do
185 185 assert_equal false, @ecookbook.archive
186 186 end
187 187 @ecookbook.reload
188 188 assert @ecookbook.active?
189 189 end
190 190
191 191 def test_unarchive
192 192 user = @ecookbook.members.first.user
193 193 @ecookbook.archive
194 194 # A subproject of an archived project can not be unarchived
195 195 assert !@ecookbook_sub1.unarchive
196 196
197 197 # Unarchive project
198 198 assert @ecookbook.unarchive
199 199 @ecookbook.reload
200 200 assert @ecookbook.active?
201 201 assert !@ecookbook.archived?
202 202 assert user.projects.include?(@ecookbook)
203 203 # Subproject can now be unarchived
204 204 @ecookbook_sub1.reload
205 205 assert @ecookbook_sub1.unarchive
206 206 end
207 207
208 208 def test_destroy
209 209 # 2 active members
210 210 assert_equal 2, @ecookbook.members.size
211 211 # and 1 is locked
212 212 assert_equal 3, Member.where(:project_id => @ecookbook.id).count
213 213 # some boards
214 214 assert @ecookbook.boards.any?
215 215
216 216 @ecookbook.destroy
217 217 # make sure that the project non longer exists
218 218 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
219 219 # make sure related data was removed
220 220 assert_nil Member.where(:project_id => @ecookbook.id).first
221 221 assert_nil Board.where(:project_id => @ecookbook.id).first
222 222 assert_nil Issue.where(:project_id => @ecookbook.id).first
223 223 end
224 224
225 225 def test_destroy_should_destroy_subtasks
226 226 issues = (0..2).to_a.map {Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test')}
227 227 issues[0].update! :parent_issue_id => issues[1].id
228 228 issues[2].update! :parent_issue_id => issues[1].id
229 229 assert_equal 2, issues[1].children.count
230 230
231 231 assert_nothing_raised do
232 232 Project.find(1).destroy
233 233 end
234 234 assert_equal 0, Issue.where(:id => issues.map(&:id)).count
235 235 end
236 236
237 237 def test_destroying_root_projects_should_clear_data
238 238 Project.roots.each do |root|
239 239 root.destroy
240 240 end
241 241
242 242 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
243 243 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
244 244 assert_equal 0, MemberRole.count
245 245 assert_equal 0, Issue.count
246 246 assert_equal 0, Journal.count
247 247 assert_equal 0, JournalDetail.count
248 248 assert_equal 0, Attachment.count, "Attachments were not deleted: #{Attachment.all.inspect}"
249 249 assert_equal 0, EnabledModule.count
250 250 assert_equal 0, IssueCategory.count
251 251 assert_equal 0, IssueRelation.count
252 252 assert_equal 0, Board.count
253 253 assert_equal 0, Message.count
254 254 assert_equal 0, News.count
255 255 assert_equal 0, Query.where("project_id IS NOT NULL").count
256 256 assert_equal 0, Repository.count
257 257 assert_equal 0, Changeset.count
258 258 assert_equal 0, Change.count
259 259 assert_equal 0, Comment.count
260 260 assert_equal 0, TimeEntry.count
261 261 assert_equal 0, Version.count
262 262 assert_equal 0, Watcher.count
263 263 assert_equal 0, Wiki.count
264 264 assert_equal 0, WikiPage.count
265 265 assert_equal 0, WikiContent.count
266 266 assert_equal 0, WikiContent::Version.count
267 267 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").count
268 268 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").count
269 269 assert_equal 0, CustomValue.where(:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']).count
270 270 end
271 271
272 272 def test_destroy_should_delete_time_entries_custom_values
273 273 project = Project.generate!
274 274 time_entry = TimeEntry.generate!(:project => project, :custom_field_values => {10 => '1'})
275 275
276 276 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
277 277 assert project.destroy
278 278 end
279 279 end
280 280
281 281 def test_move_an_orphan_project_to_a_root_project
282 282 sub = Project.find(2)
283 283 sub.set_parent! @ecookbook
284 284 assert_equal @ecookbook.id, sub.parent.id
285 285 @ecookbook.reload
286 286 assert_equal 4, @ecookbook.children.size
287 287 end
288 288
289 289 def test_move_an_orphan_project_to_a_subproject
290 290 sub = Project.find(2)
291 291 assert sub.set_parent!(@ecookbook_sub1)
292 292 end
293 293
294 294 def test_move_a_root_project_to_a_project
295 295 sub = @ecookbook
296 296 assert sub.set_parent!(Project.find(2))
297 297 end
298 298
299 299 def test_should_not_move_a_project_to_its_children
300 300 sub = @ecookbook
301 301 assert !(sub.set_parent!(Project.find(3)))
302 302 end
303 303
304 304 def test_set_parent_should_add_roots_in_alphabetical_order
305 305 ProjectCustomField.delete_all
306 306 Project.delete_all
307 307 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
308 308 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
309 309 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
310 310 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
311 311
312 312 assert_equal 4, Project.count
313 313 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
314 314 end
315 315
316 316 def test_set_parent_should_add_children_in_alphabetical_order
317 317 ProjectCustomField.delete_all
318 318 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
319 319 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
320 320 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
321 321 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
322 322 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
323 323
324 324 parent.reload
325 325 assert_equal 4, parent.children.size
326 326 assert_equal parent.children.sort_by(&:name), parent.children.to_a
327 327 end
328 328
329 329 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
330 330 # Parent issue with a hierarchy project's fixed version
331 331 parent_issue = Issue.find(1)
332 332 parent_issue.update_attribute(:fixed_version_id, 4)
333 333 parent_issue.reload
334 334 assert_equal 4, parent_issue.fixed_version_id
335 335
336 336 # Should keep fixed versions for the issues
337 337 issue_with_local_fixed_version = Issue.find(5)
338 338 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
339 339 issue_with_local_fixed_version.reload
340 340 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
341 341
342 342 # Local issue with hierarchy fixed_version
343 343 issue_with_hierarchy_fixed_version = Issue.find(13)
344 344 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
345 345 issue_with_hierarchy_fixed_version.reload
346 346 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
347 347
348 348 # Move project out of the issue's hierarchy
349 349 moved_project = Project.find(3)
350 350 moved_project.set_parent!(Project.find(2))
351 351 parent_issue.reload
352 352 issue_with_local_fixed_version.reload
353 353 issue_with_hierarchy_fixed_version.reload
354 354
355 355 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
356 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
357 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
356 assert_nil issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
357 assert_nil parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
358 358 end
359 359
360 360 def test_parent
361 361 p = Project.find(6).parent
362 362 assert p.is_a?(Project)
363 363 assert_equal 5, p.id
364 364 end
365 365
366 366 def test_ancestors
367 367 a = Project.find(6).ancestors
368 368 assert a.first.is_a?(Project)
369 369 assert_equal [1, 5], a.collect(&:id)
370 370 end
371 371
372 372 def test_root
373 373 r = Project.find(6).root
374 374 assert r.is_a?(Project)
375 375 assert_equal 1, r.id
376 376 end
377 377
378 378 def test_children
379 379 c = Project.find(1).children
380 380 assert c.first.is_a?(Project)
381 381 assert_equal [5, 3, 4], c.collect(&:id)
382 382 end
383 383
384 384 def test_descendants
385 385 d = Project.find(1).descendants
386 386 assert d.first.is_a?(Project)
387 387 assert_equal [5, 6, 3, 4], d.collect(&:id)
388 388 end
389 389
390 390 def test_allowed_parents_should_be_empty_for_non_member_user
391 391 Role.non_member.add_permission!(:add_project)
392 392 user = User.find(9)
393 393 assert user.memberships.empty?
394 394 User.current = user
395 395 assert Project.new.allowed_parents.compact.empty?
396 396 end
397 397
398 398 def test_allowed_parents_with_add_subprojects_permission
399 399 Role.find(1).remove_permission!(:add_project)
400 400 Role.find(1).add_permission!(:add_subprojects)
401 401 User.current = User.find(2)
402 402 # new project
403 403 assert !Project.new.allowed_parents.include?(nil)
404 404 assert Project.new.allowed_parents.include?(Project.find(1))
405 405 # existing root project
406 406 assert Project.find(1).allowed_parents.include?(nil)
407 407 # existing child
408 408 assert Project.find(3).allowed_parents.include?(Project.find(1))
409 409 assert !Project.find(3).allowed_parents.include?(nil)
410 410 end
411 411
412 412 def test_allowed_parents_with_add_project_permission
413 413 Role.find(1).add_permission!(:add_project)
414 414 Role.find(1).remove_permission!(:add_subprojects)
415 415 User.current = User.find(2)
416 416 # new project
417 417 assert Project.new.allowed_parents.include?(nil)
418 418 assert !Project.new.allowed_parents.include?(Project.find(1))
419 419 # existing root project
420 420 assert Project.find(1).allowed_parents.include?(nil)
421 421 # existing child
422 422 assert Project.find(3).allowed_parents.include?(Project.find(1))
423 423 assert Project.find(3).allowed_parents.include?(nil)
424 424 end
425 425
426 426 def test_allowed_parents_with_add_project_and_subprojects_permission
427 427 Role.find(1).add_permission!(:add_project)
428 428 Role.find(1).add_permission!(:add_subprojects)
429 429 User.current = User.find(2)
430 430 # new project
431 431 assert Project.new.allowed_parents.include?(nil)
432 432 assert Project.new.allowed_parents.include?(Project.find(1))
433 433 # existing root project
434 434 assert Project.find(1).allowed_parents.include?(nil)
435 435 # existing child
436 436 assert Project.find(3).allowed_parents.include?(Project.find(1))
437 437 assert Project.find(3).allowed_parents.include?(nil)
438 438 end
439 439
440 440 def test_users_by_role
441 441 users_by_role = Project.find(1).users_by_role
442 442 assert_kind_of Hash, users_by_role
443 443 role = Role.find(1)
444 444 assert_kind_of Array, users_by_role[role]
445 445 assert users_by_role[role].include?(User.find(2))
446 446 end
447 447
448 448 def test_rolled_up_trackers
449 449 parent = Project.find(1)
450 450 parent.trackers = Tracker.find([1,2])
451 451 child = parent.children.find(3)
452 452
453 453 assert_equal [1, 2], parent.tracker_ids
454 454 assert_equal [2, 3], child.trackers.collect(&:id)
455 455
456 456 assert_kind_of Tracker, parent.rolled_up_trackers.first
457 457 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
458 458
459 459 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
460 460 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
461 461 end
462 462
463 463 def test_rolled_up_trackers_should_ignore_archived_subprojects
464 464 parent = Project.find(1)
465 465 parent.trackers = Tracker.find([1,2])
466 466 child = parent.children.find(3)
467 467 child.trackers = Tracker.find([1,3])
468 468 parent.children.each(&:archive)
469 469
470 470 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
471 471 end
472 472
473 473 test "#rolled_up_trackers should ignore projects with issue_tracking module disabled" do
474 474 parent = Project.generate!
475 475 parent.trackers = Tracker.find([1, 2])
476 476 child = Project.generate_with_parent!(parent)
477 477 child.trackers = Tracker.find([2, 3])
478 478
479 479 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id).sort
480 480
481 481 assert child.disable_module!(:issue_tracking)
482 482 parent.reload
483 483 assert_equal [1, 2], parent.rolled_up_trackers.collect(&:id).sort
484 484 end
485 485
486 486 test "#rolled_up_versions should include the versions for the current project" do
487 487 project = Project.generate!
488 488 parent_version_1 = Version.generate!(:project => project)
489 489 parent_version_2 = Version.generate!(:project => project)
490 490 assert_equal [parent_version_1, parent_version_2].sort,
491 491 project.rolled_up_versions.sort
492 492 end
493 493
494 494 test "#rolled_up_versions should include versions for a subproject" do
495 495 project = Project.generate!
496 496 parent_version_1 = Version.generate!(:project => project)
497 497 parent_version_2 = Version.generate!(:project => project)
498 498 subproject = Project.generate_with_parent!(project)
499 499 subproject_version = Version.generate!(:project => subproject)
500 500
501 501 assert_equal [parent_version_1, parent_version_2, subproject_version].sort,
502 502 project.rolled_up_versions.sort
503 503 end
504 504
505 505 test "#rolled_up_versions should include versions for a sub-subproject" do
506 506 project = Project.generate!
507 507 parent_version_1 = Version.generate!(:project => project)
508 508 parent_version_2 = Version.generate!(:project => project)
509 509 subproject = Project.generate_with_parent!(project)
510 510 sub_subproject = Project.generate_with_parent!(subproject)
511 511 sub_subproject_version = Version.generate!(:project => sub_subproject)
512 512 project.reload
513 513
514 514 assert_equal [parent_version_1, parent_version_2, sub_subproject_version].sort,
515 515 project.rolled_up_versions.sort
516 516 end
517 517
518 518 test "#rolled_up_versions should only check active projects" do
519 519 project = Project.generate!
520 520 parent_version_1 = Version.generate!(:project => project)
521 521 parent_version_2 = Version.generate!(:project => project)
522 522 subproject = Project.generate_with_parent!(project)
523 523 subproject_version = Version.generate!(:project => subproject)
524 524 assert subproject.archive
525 525 project.reload
526 526
527 527 assert !subproject.active?
528 528 assert_equal [parent_version_1, parent_version_2].sort,
529 529 project.rolled_up_versions.sort
530 530 end
531 531
532 532 def test_shared_versions_none_sharing
533 533 p = Project.find(5)
534 534 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
535 535 assert p.shared_versions.include?(v)
536 536 assert !p.children.first.shared_versions.include?(v)
537 537 assert !p.root.shared_versions.include?(v)
538 538 assert !p.siblings.first.shared_versions.include?(v)
539 539 assert !p.root.siblings.first.shared_versions.include?(v)
540 540 end
541 541
542 542 def test_shared_versions_descendants_sharing
543 543 p = Project.find(5)
544 544 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
545 545 assert p.shared_versions.include?(v)
546 546 assert p.children.first.shared_versions.include?(v)
547 547 assert !p.root.shared_versions.include?(v)
548 548 assert !p.siblings.first.shared_versions.include?(v)
549 549 assert !p.root.siblings.first.shared_versions.include?(v)
550 550 end
551 551
552 552 def test_shared_versions_hierarchy_sharing
553 553 p = Project.find(5)
554 554 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
555 555 assert p.shared_versions.include?(v)
556 556 assert p.children.first.shared_versions.include?(v)
557 557 assert p.root.shared_versions.include?(v)
558 558 assert !p.siblings.first.shared_versions.include?(v)
559 559 assert !p.root.siblings.first.shared_versions.include?(v)
560 560 end
561 561
562 562 def test_shared_versions_tree_sharing
563 563 p = Project.find(5)
564 564 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
565 565 assert p.shared_versions.include?(v)
566 566 assert p.children.first.shared_versions.include?(v)
567 567 assert p.root.shared_versions.include?(v)
568 568 assert p.siblings.first.shared_versions.include?(v)
569 569 assert !p.root.siblings.first.shared_versions.include?(v)
570 570 end
571 571
572 572 def test_shared_versions_system_sharing
573 573 p = Project.find(5)
574 574 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
575 575 assert p.shared_versions.include?(v)
576 576 assert p.children.first.shared_versions.include?(v)
577 577 assert p.root.shared_versions.include?(v)
578 578 assert p.siblings.first.shared_versions.include?(v)
579 579 assert p.root.siblings.first.shared_versions.include?(v)
580 580 end
581 581
582 582 def test_shared_versions
583 583 parent = Project.find(1)
584 584 child = parent.children.find(3)
585 585 private_child = parent.children.find(5)
586 586
587 587 assert_equal [1,2,3], parent.version_ids.sort
588 588 assert_equal [4], child.version_ids
589 589 assert_equal [6], private_child.version_ids
590 590 assert_equal [7], Version.where(:sharing => 'system').collect(&:id)
591 591
592 592 assert_equal 6, parent.shared_versions.size
593 593 parent.shared_versions.each do |version|
594 594 assert_kind_of Version, version
595 595 end
596 596
597 597 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
598 598 end
599 599
600 600 def test_shared_versions_should_ignore_archived_subprojects
601 601 parent = Project.find(1)
602 602 child = parent.children.find(3)
603 603 child.archive
604 604 parent.reload
605 605
606 606 assert_equal [1,2,3], parent.version_ids.sort
607 607 assert_equal [4], child.version_ids
608 608 assert !parent.shared_versions.collect(&:id).include?(4)
609 609 end
610 610
611 611 def test_shared_versions_visible_to_user
612 612 user = User.find(3)
613 613 parent = Project.find(1)
614 614 child = parent.children.find(5)
615 615
616 616 assert_equal [1,2,3], parent.version_ids.sort
617 617 assert_equal [6], child.version_ids
618 618
619 619 versions = parent.shared_versions.visible(user)
620 620
621 621 assert_equal 4, versions.size
622 622 versions.each do |version|
623 623 assert_kind_of Version, version
624 624 end
625 625
626 626 assert !versions.collect(&:id).include?(6)
627 627 end
628 628
629 629 def test_shared_versions_for_new_project_should_include_system_shared_versions
630 630 p = Project.find(5)
631 631 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
632 632
633 633 assert_include v, Project.new.shared_versions
634 634 end
635 635
636 636 def test_next_identifier
637 637 ProjectCustomField.delete_all
638 638 Project.create!(:name => 'last', :identifier => 'p2008040')
639 639 assert_equal 'p2008041', Project.next_identifier
640 640 end
641 641
642 642 def test_next_identifier_first_project
643 643 Project.delete_all
644 644 assert_nil Project.next_identifier
645 645 end
646 646
647 647 def test_enabled_module_names
648 648 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
649 649 project = Project.new
650 650
651 651 project.enabled_module_names = %w(issue_tracking news)
652 652 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
653 653 end
654 654 end
655 655
656 656 def test_enabled_modules_names_with_nil_should_clear_modules
657 657 p = Project.find(1)
658 658 p.enabled_module_names = nil
659 659 assert_equal [], p.enabled_modules
660 660 end
661 661
662 662 test "enabled_modules should define module by names and preserve ids" do
663 663 @project = Project.find(1)
664 664 # Remove one module
665 665 modules = @project.enabled_modules.to_a.slice(0..-2)
666 666 assert modules.any?
667 667 assert_difference 'EnabledModule.count', -1 do
668 668 @project.enabled_module_names = modules.collect(&:name)
669 669 end
670 670 @project.reload
671 671 # Ids should be preserved
672 672 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
673 673 end
674 674
675 675 test "enabled_modules should enable a module" do
676 676 @project = Project.find(1)
677 677 @project.enabled_module_names = []
678 678 @project.reload
679 679 assert_equal [], @project.enabled_module_names
680 680 #with string
681 681 @project.enable_module!("issue_tracking")
682 682 assert_equal ["issue_tracking"], @project.enabled_module_names
683 683 #with symbol
684 684 @project.enable_module!(:gantt)
685 685 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
686 686 #don't add a module twice
687 687 @project.enable_module!("issue_tracking")
688 688 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
689 689 end
690 690
691 691 test "enabled_modules should disable a module" do
692 692 @project = Project.find(1)
693 693 #with string
694 694 assert @project.enabled_module_names.include?("issue_tracking")
695 695 @project.disable_module!("issue_tracking")
696 696 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
697 697 #with symbol
698 698 assert @project.enabled_module_names.include?("gantt")
699 699 @project.disable_module!(:gantt)
700 700 assert ! @project.reload.enabled_module_names.include?("gantt")
701 701 #with EnabledModule object
702 702 first_module = @project.enabled_modules.first
703 703 @project.disable_module!(first_module)
704 704 assert ! @project.reload.enabled_module_names.include?(first_module.name)
705 705 end
706 706
707 707 def test_enabled_module_names_should_not_recreate_enabled_modules
708 708 project = Project.find(1)
709 709 # Remove one module
710 710 modules = project.enabled_modules.to_a.slice(0..-2)
711 711 assert modules.any?
712 712 assert_difference 'EnabledModule.count', -1 do
713 713 project.enabled_module_names = modules.collect(&:name)
714 714 end
715 715 project.reload
716 716 # Ids should be preserved
717 717 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
718 718 end
719 719
720 720 def test_copy_from_existing_project
721 721 source_project = Project.find(1)
722 722 copied_project = Project.copy_from(1)
723 723
724 724 assert copied_project
725 725 # Cleared attributes
726 726 assert copied_project.id.blank?
727 727 assert copied_project.name.blank?
728 728 assert copied_project.identifier.blank?
729 729
730 730 # Duplicated attributes
731 731 assert_equal source_project.description, copied_project.description
732 732 assert_equal source_project.trackers, copied_project.trackers
733 733
734 734 # Default attributes
735 735 assert_equal 1, copied_project.status
736 736 end
737 737
738 738 def test_copy_from_should_copy_enabled_modules
739 739 source = Project.generate!
740 740 source.enabled_module_names = %w(issue_tracking wiki)
741 741
742 742 copy = Project.copy_from(source)
743 743 copy.name = 'Copy'
744 744 copy.identifier = 'copy'
745 745 assert_difference 'EnabledModule.count', 2 do
746 746 copy.save!
747 747 end
748 748 assert_equal 2, copy.reload.enabled_modules.count
749 749 assert_equal 2, source.reload.enabled_modules.count
750 750 end
751 751
752 752 def test_activities_should_use_the_system_activities
753 753 project = Project.find(1)
754 754 assert_equal project.activities.to_a, TimeEntryActivity.where(:active => true).to_a
755 755 assert_kind_of ActiveRecord::Relation, project.activities
756 756 end
757 757
758 758
759 759 def test_activities_should_use_the_project_specific_activities
760 760 project = Project.find(1)
761 761 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
762 762 assert overridden_activity.save!
763 763
764 764 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
765 765 assert_kind_of ActiveRecord::Relation, project.activities
766 766 end
767 767
768 768 def test_activities_should_not_include_the_inactive_project_specific_activities
769 769 project = Project.find(1)
770 770 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
771 771 assert overridden_activity.save!
772 772
773 773 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
774 774 end
775 775
776 776 def test_activities_should_not_include_project_specific_activities_from_other_projects
777 777 project = Project.find(1)
778 778 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
779 779 assert overridden_activity.save!
780 780
781 781 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
782 782 end
783 783
784 784 def test_activities_should_handle_nils
785 785 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first})
786 786 TimeEntryActivity.delete_all
787 787
788 788 # No activities
789 789 project = Project.find(1)
790 790 assert project.activities.empty?
791 791
792 792 # No system, one overridden
793 793 assert overridden_activity.save!
794 794 project.reload
795 795 assert_equal [overridden_activity], project.activities
796 796 end
797 797
798 798 def test_activities_should_override_system_activities_with_project_activities
799 799 project = Project.find(1)
800 800 parent_activity = TimeEntryActivity.first
801 801 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
802 802 assert overridden_activity.save!
803 803
804 804 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
805 805 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
806 806 end
807 807
808 808 def test_activities_should_include_inactive_activities_if_specified
809 809 project = Project.find(1)
810 810 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
811 811 assert overridden_activity.save!
812 812
813 813 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
814 814 end
815 815
816 816 test 'activities should not include active System activities if the project has an override that is inactive' do
817 817 project = Project.find(1)
818 818 system_activity = TimeEntryActivity.find_by_name('Design')
819 819 assert system_activity.active?
820 820 overridden_activity = TimeEntryActivity.create!(:name => "Project", :project => project, :parent => system_activity, :active => false)
821 821 assert overridden_activity.save!
822 822
823 823 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
824 824 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
825 825 end
826 826
827 827 def test_close_completed_versions
828 828 Version.update_all("status = 'open'")
829 829 project = Project.find(1)
830 830 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
831 831 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
832 832 project.close_completed_versions
833 833 project.reload
834 834 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
835 835 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
836 836 end
837 837
838 838 test "#start_date should be nil if there are no issues on the project" do
839 839 project = Project.generate!
840 840 assert_nil project.start_date
841 841 end
842 842
843 843 test "#start_date should be nil when issues have no start date" do
844 844 project = Project.generate!
845 845 project.trackers << Tracker.generate!
846 846 early = 7.days.ago.to_date
847 847 Issue.generate!(:project => project, :start_date => nil)
848 848
849 849 assert_nil project.start_date
850 850 end
851 851
852 852 test "#start_date should be the earliest start date of it's issues" do
853 853 project = Project.generate!
854 854 project.trackers << Tracker.generate!
855 855 early = 7.days.ago.to_date
856 856 Issue.generate!(:project => project, :start_date => Date.today)
857 857 Issue.generate!(:project => project, :start_date => early)
858 858
859 859 assert_equal early, project.start_date
860 860 end
861 861
862 862 test "#due_date should be nil if there are no issues on the project" do
863 863 project = Project.generate!
864 864 assert_nil project.due_date
865 865 end
866 866
867 867 test "#due_date should be nil if there are no issues with due dates" do
868 868 project = Project.generate!
869 869 project.trackers << Tracker.generate!
870 870 Issue.generate!(:project => project, :due_date => nil)
871 871
872 872 assert_nil project.due_date
873 873 end
874 874
875 875 test "#due_date should be the latest due date of it's issues" do
876 876 project = Project.generate!
877 877 project.trackers << Tracker.generate!
878 878 future = 7.days.from_now.to_date
879 879 Issue.generate!(:project => project, :due_date => future)
880 880 Issue.generate!(:project => project, :due_date => Date.today)
881 881
882 882 assert_equal future, project.due_date
883 883 end
884 884
885 885 test "#due_date should be the latest due date of it's versions" do
886 886 project = Project.generate!
887 887 future = 7.days.from_now.to_date
888 888 project.versions << Version.generate!(:effective_date => future)
889 889 project.versions << Version.generate!(:effective_date => Date.today)
890 890
891 891 assert_equal future, project.due_date
892 892 end
893 893
894 894 test "#due_date should pick the latest date from it's issues and versions" do
895 895 project = Project.generate!
896 896 project.trackers << Tracker.generate!
897 897 future = 7.days.from_now.to_date
898 898 far_future = 14.days.from_now.to_date
899 899 Issue.generate!(:project => project, :due_date => far_future)
900 900 project.versions << Version.generate!(:effective_date => future)
901 901
902 902 assert_equal far_future, project.due_date
903 903 end
904 904
905 905 test "#completed_percent with no versions should be 100" do
906 906 project = Project.generate!
907 907 assert_equal 100, project.completed_percent
908 908 end
909 909
910 910 test "#completed_percent with versions should return 0 if the versions have no issues" do
911 911 project = Project.generate!
912 912 Version.generate!(:project => project)
913 913 Version.generate!(:project => project)
914 914
915 915 assert_equal 0, project.completed_percent
916 916 end
917 917
918 918 test "#completed_percent with versions should return 100 if the version has only closed issues" do
919 919 project = Project.generate!
920 920 project.trackers << Tracker.generate!
921 921 v1 = Version.generate!(:project => project)
922 922 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
923 923 v2 = Version.generate!(:project => project)
924 924 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
925 925
926 926 assert_equal 100, project.completed_percent
927 927 end
928 928
929 929 test "#completed_percent with versions should return the averaged completed percent of the versions (not weighted)" do
930 930 project = Project.generate!
931 931 project.trackers << Tracker.generate!
932 932 v1 = Version.generate!(:project => project)
933 933 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
934 934 v2 = Version.generate!(:project => project)
935 935 Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
936 936
937 937 assert_equal 50, project.completed_percent
938 938 end
939 939
940 940 test "#notified_users" do
941 941 project = Project.generate!
942 942 role = Role.generate!
943 943
944 944 user_with_membership_notification = User.generate!(:mail_notification => 'selected')
945 945 Member.create!(:project => project, :roles => [role], :principal => user_with_membership_notification, :mail_notification => true)
946 946
947 947 all_events_user = User.generate!(:mail_notification => 'all')
948 948 Member.create!(:project => project, :roles => [role], :principal => all_events_user)
949 949
950 950 no_events_user = User.generate!(:mail_notification => 'none')
951 951 Member.create!(:project => project, :roles => [role], :principal => no_events_user)
952 952
953 953 only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
954 954 Member.create!(:project => project, :roles => [role], :principal => only_my_events_user)
955 955
956 956 only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
957 957 Member.create!(:project => project, :roles => [role], :principal => only_assigned_user)
958 958
959 959 only_owned_user = User.generate!(:mail_notification => 'only_owner')
960 960 Member.create!(:project => project, :roles => [role], :principal => only_owned_user)
961 961
962 962 assert project.notified_users.include?(user_with_membership_notification), "should include members with a mail notification"
963 963 assert project.notified_users.include?(all_events_user), "should include users with the 'all' notification option"
964 964 assert !project.notified_users.include?(no_events_user), "should not include users with the 'none' notification option"
965 965 assert !project.notified_users.include?(only_my_events_user), "should not include users with the 'only_my_events' notification option"
966 966 assert !project.notified_users.include?(only_assigned_user), "should not include users with the 'only_assigned' notification option"
967 967 assert !project.notified_users.include?(only_owned_user), "should not include users with the 'only_owner' notification option"
968 968 end
969 969
970 970 def test_override_roles_without_builtin_group_memberships
971 971 project = Project.generate!
972 972 assert_equal [Role.anonymous], project.override_roles(Role.anonymous)
973 973 assert_equal [Role.non_member], project.override_roles(Role.non_member)
974 974 end
975 975
976 976 def test_css_classes
977 977 p = Project.new
978 978 assert_kind_of String, p.css_classes
979 979 assert_not_include 'archived', p.css_classes.split
980 980 assert_not_include 'closed', p.css_classes.split
981 981 end
982 982
983 983 def test_css_classes_for_archived_project
984 984 p = Project.new
985 985 p.status = Project::STATUS_ARCHIVED
986 986 assert_include 'archived', p.css_classes.split
987 987 end
988 988
989 989 def test_css_classes_for_closed_project
990 990 p = Project.new
991 991 p.status = Project::STATUS_CLOSED
992 992 assert_include 'closed', p.css_classes.split
993 993 end
994 994
995 995 def test_combination_of_visible_and_distinct_scopes_in_case_anonymous_group_has_memberships_should_not_error
996 996 project = Project.find(1)
997 997 member = Member.create!(:project => project, :principal => Group.anonymous, :roles => [Role.generate!])
998 998 project.members << member
999 999 assert_nothing_raised do
1000 1000 Project.distinct.visible.to_a
1001 1001 end
1002 1002 end
1003 1003 end
@@ -1,614 +1,614
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoryGitTest < ActiveSupport::TestCase
21 21 fixtures :projects, :repositories, :enabled_modules, :users, :roles
22 22
23 23 include Redmine::I18n
24 24
25 25 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
26 26 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
27 27
28 28 NUM_REV = 28
29 29 NUM_HEAD = 6
30 30
31 31 FELIX_HEX = "Felix Sch\xC3\xA4fer".force_encoding('UTF-8')
32 32 CHAR_1_HEX = "\xc3\x9c".force_encoding('UTF-8')
33 33
34 34 ## Git, Mercurial and CVS path encodings are binary.
35 35 ## Subversion supports URL encoding for path.
36 36 ## Redmine Mercurial adapter and extension use URL encoding.
37 37 ## Git accepts only binary path in command line parameter.
38 38 ## So, there is no way to use binary command line parameter in JRuby.
39 39 JRUBY_SKIP = (RUBY_PLATFORM == 'java')
40 40 JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
41 41
42 42 def setup
43 43 @project = Project.find(3)
44 44 @repository = Repository::Git.create(
45 45 :project => @project,
46 46 :url => REPOSITORY_PATH,
47 47 :path_encoding => 'ISO-8859-1'
48 48 )
49 49 assert @repository
50 50 end
51 51
52 52 def test_nondefault_repo_with_blank_identifier_destruction
53 53 Repository.delete_all
54 54
55 55 repo1 = Repository::Git.new(
56 56 :project => @project,
57 57 :url => REPOSITORY_PATH,
58 58 :identifier => '',
59 59 :is_default => true
60 60 )
61 61 assert repo1.save
62 62 repo1.fetch_changesets
63 63
64 64 repo2 = Repository::Git.new(
65 65 :project => @project,
66 66 :url => REPOSITORY_PATH,
67 67 :identifier => 'repo2',
68 68 :is_default => true
69 69 )
70 70 assert repo2.save
71 71 repo2.fetch_changesets
72 72
73 73 repo1.reload
74 74 repo2.reload
75 75 assert !repo1.is_default?
76 76 assert repo2.is_default?
77 77
78 78 assert_difference 'Repository.count', -1 do
79 79 repo1.destroy
80 80 end
81 81 end
82 82
83 83 def test_blank_path_to_repository_error_message
84 84 set_language_if_valid 'en'
85 85 repo = Repository::Git.new(
86 86 :project => @project,
87 87 :identifier => 'test'
88 88 )
89 89 assert !repo.save
90 90 assert_include "Path to repository cannot be blank",
91 91 repo.errors.full_messages
92 92 end
93 93
94 94 def test_blank_path_to_repository_error_message_fr
95 95 set_language_if_valid 'fr'
96 96 str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
97 97 repo = Repository::Git.new(
98 98 :project => @project,
99 99 :url => "",
100 100 :identifier => 'test',
101 101 :path_encoding => ''
102 102 )
103 103 assert !repo.save
104 104 assert_include str, repo.errors.full_messages
105 105 end
106 106
107 107 if File.directory?(REPOSITORY_PATH)
108 108 ## Ruby uses ANSI api to fork a process on Windows.
109 109 ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
110 110 ## and these are incompatible with ASCII.
111 111 ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
112 112 ## http://code.google.com/p/msysgit/issues/detail?id=80
113 113 ## So, Latin-1 path tests fail on Japanese Windows
114 114 WINDOWS_PASS = (Redmine::Platform.mswin? &&
115 115 Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
116 116 WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
117 117
118 118 def test_scm_available
119 119 klass = Repository::Git
120 120 assert_equal "Git", klass.scm_name
121 121 assert klass.scm_adapter_class
122 122 assert_not_equal "", klass.scm_command
123 123 assert_equal true, klass.scm_available
124 124 end
125 125
126 126 def test_entries
127 127 entries = @repository.entries
128 128 assert_kind_of Redmine::Scm::Adapters::Entries, entries
129 129 end
130 130
131 131 def test_fetch_changesets_from_scratch
132 132 assert_nil @repository.extra_info
133 133
134 134 assert_equal 0, @repository.changesets.count
135 135 @repository.fetch_changesets
136 136 @project.reload
137 137
138 138 assert_equal NUM_REV, @repository.changesets.count
139 139 assert_equal 39, @repository.filechanges.count
140 140
141 141 commit = @repository.changesets.find_by_revision("7234cb2750b63f47bff735edc50a1c0a433c2518")
142 142 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
143 143 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
144 144 assert_equal "jsmith <jsmith@foo.bar>", commit.committer
145 145 assert_equal User.find_by_login('jsmith'), commit.user
146 146 # TODO: add a commit with commit time <> author time to the test repository
147 147 assert_equal Time.gm(2007, 12, 14, 9, 22, 52), commit.committed_on
148 148 assert_equal "2007-12-14".to_date, commit.commit_date
149 149 assert_equal 3, commit.filechanges.count
150 150 change = commit.filechanges.sort_by(&:path).first
151 151 assert_equal "README", change.path
152 assert_equal nil, change.from_path
152 assert_nil change.from_path
153 153 assert_equal "A", change.action
154 154
155 155 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
156 156 end
157 157
158 158 def test_fetch_changesets_incremental
159 159 assert_equal 0, @repository.changesets.count
160 160 @repository.fetch_changesets
161 161 @project.reload
162 162 assert_equal NUM_REV, @repository.changesets.count
163 163 extra_info_heads = @repository.extra_info["heads"].dup
164 164 assert_equal NUM_HEAD, extra_info_heads.size
165 165 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
166 166 assert_equal 4, extra_info_heads.size
167 167
168 168 del_revs = [
169 169 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
170 170 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
171 171 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
172 172 "deff712f05a90d96edbd70facc47d944be5897e3",
173 173 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
174 174 "7e61ac704deecde634b51e59daa8110435dcb3da",
175 175 ]
176 176 @repository.changesets.each do |rev|
177 177 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
178 178 end
179 179 @project.reload
180 180 cs1 = @repository.changesets
181 181 assert_equal NUM_REV - 6, cs1.count
182 182 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
183 183 h = {}
184 184 h["heads"] = extra_info_heads
185 185 @repository.merge_extra_info(h)
186 186 @repository.save
187 187 @project.reload
188 188 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
189 189 @repository.fetch_changesets
190 190 @project.reload
191 191 assert_equal NUM_REV, @repository.changesets.count
192 192 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
193 193 assert @repository.extra_info["heads"].index("83ca5fd546063a3c7dc2e568ba3355661a9e2b2c")
194 194 end
195 195
196 196 def test_fetch_changesets_history_editing
197 197 assert_equal 0, @repository.changesets.count
198 198 @repository.fetch_changesets
199 199 @project.reload
200 200 assert_equal NUM_REV, @repository.changesets.count
201 201 extra_info_heads = @repository.extra_info["heads"].dup
202 202 assert_equal NUM_HEAD, extra_info_heads.size
203 203 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
204 204 assert_equal 4, extra_info_heads.size
205 205
206 206 del_revs = [
207 207 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
208 208 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
209 209 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
210 210 "deff712f05a90d96edbd70facc47d944be5897e3",
211 211 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
212 212 "7e61ac704deecde634b51e59daa8110435dcb3da",
213 213 ]
214 214 @repository.changesets.each do |rev|
215 215 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
216 216 end
217 217 @project.reload
218 218 assert_equal NUM_REV - 6, @repository.changesets.count
219 219
220 220 c = Changeset.new(:repository => @repository,
221 221 :committed_on => Time.now,
222 222 :revision => "abcd1234efgh",
223 223 :scmid => "abcd1234efgh",
224 224 :comments => 'test')
225 225 assert c.save
226 226 @project.reload
227 227 assert_equal NUM_REV - 5, @repository.changesets.count
228 228
229 229 extra_info_heads << "1234abcd5678"
230 230 h = {}
231 231 h["heads"] = extra_info_heads
232 232 @repository.merge_extra_info(h)
233 233 @repository.save
234 234 @project.reload
235 235 h1 = @repository.extra_info["heads"].dup
236 236 assert h1.index("1234abcd5678")
237 237 assert_equal 5, h1.size
238 238
239 239 @repository.fetch_changesets
240 240 @project.reload
241 241 assert_equal NUM_REV - 5, @repository.changesets.count
242 242 h2 = @repository.extra_info["heads"].dup
243 243 assert_equal h1, h2
244 244 end
245 245
246 246 def test_clear_changesets_should_keep_report_last_commit
247 247 assert_nil @repository.extra_info
248 248 @repository.report_last_commit = "1"
249 249 @repository.save
250 250 @repository.send(:clear_changesets)
251 251
252 252 assert_equal true, @repository.report_last_commit
253 253 end
254 254
255 255 def test_refetch_after_clear_changesets
256 256 assert_nil @repository.extra_info
257 257 assert_equal 0, @repository.changesets.count
258 258 @repository.fetch_changesets
259 259 @project.reload
260 260 assert_equal NUM_REV, @repository.changesets.count
261 261
262 262 @repository.send(:clear_changesets)
263 263 @project.reload
264 264 assert_equal 0, @repository.changesets.count
265 265
266 266 @repository.fetch_changesets
267 267 @project.reload
268 268 assert_equal NUM_REV, @repository.changesets.count
269 269 end
270 270
271 271 def test_parents
272 272 assert_equal 0, @repository.changesets.count
273 273 @repository.fetch_changesets
274 274 @project.reload
275 275 assert_equal NUM_REV, @repository.changesets.count
276 276 r1 = @repository.find_changeset_by_name("7234cb2750b63")
277 277 assert_equal [], r1.parents
278 278 r2 = @repository.find_changeset_by_name("899a15dba03a3")
279 279 assert_equal 1, r2.parents.length
280 280 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
281 281 r2.parents[0].identifier
282 282 r3 = @repository.find_changeset_by_name("32ae898b720c2")
283 283 assert_equal 2, r3.parents.length
284 284 r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort
285 285 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", r4[0]
286 286 assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", r4[1]
287 287 end
288 288
289 289 def test_db_consistent_ordering_init
290 290 assert_nil @repository.extra_info
291 291 assert_equal 0, @repository.changesets.count
292 292 @repository.fetch_changesets
293 293 @project.reload
294 294 assert_equal 1, @repository.extra_info["db_consistent"]["ordering"]
295 295 end
296 296
297 297 def test_db_consistent_ordering_before_1_2
298 298 assert_nil @repository.extra_info
299 299 assert_equal 0, @repository.changesets.count
300 300 @repository.fetch_changesets
301 301 @project.reload
302 302 assert_equal NUM_REV, @repository.changesets.count
303 303 assert_not_nil @repository.extra_info
304 304 h = {}
305 305 h["heads"] = []
306 306 h["branches"] = {}
307 307 h["db_consistent"] = {}
308 308 @repository.merge_extra_info(h)
309 309 @repository.save
310 310 assert_equal NUM_REV, @repository.changesets.count
311 311 @repository.fetch_changesets
312 312 @project.reload
313 313 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
314 314
315 315 extra_info_heads = @repository.extra_info["heads"].dup
316 316 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
317 317 del_revs = [
318 318 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
319 319 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
320 320 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
321 321 "deff712f05a90d96edbd70facc47d944be5897e3",
322 322 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
323 323 "7e61ac704deecde634b51e59daa8110435dcb3da",
324 324 ]
325 325 @repository.changesets.each do |rev|
326 326 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
327 327 end
328 328 @project.reload
329 329 cs1 = @repository.changesets
330 330 assert_equal NUM_REV - 6, cs1.count
331 331 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
332 332
333 333 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
334 334 h = {}
335 335 h["heads"] = extra_info_heads
336 336 @repository.merge_extra_info(h)
337 337 @repository.save
338 338 @project.reload
339 339 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
340 340 @repository.fetch_changesets
341 341 @project.reload
342 342 assert_equal NUM_REV, @repository.changesets.count
343 343 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
344 344
345 345 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
346 346 end
347 347
348 348 def test_heads_from_branches_hash
349 349 assert_nil @repository.extra_info
350 350 assert_equal 0, @repository.changesets.count
351 351 assert_equal [], @repository.heads_from_branches_hash
352 352 h = {}
353 353 h["branches"] = {}
354 354 h["branches"]["test1"] = {}
355 355 h["branches"]["test1"]["last_scmid"] = "1234abcd"
356 356 h["branches"]["test2"] = {}
357 357 h["branches"]["test2"]["last_scmid"] = "abcd1234"
358 358 @repository.merge_extra_info(h)
359 359 @repository.save
360 360 @project.reload
361 361 assert_equal ["1234abcd", "abcd1234"], @repository.heads_from_branches_hash.sort
362 362 end
363 363
364 364 def test_latest_changesets
365 365 assert_equal 0, @repository.changesets.count
366 366 @repository.fetch_changesets
367 367 @project.reload
368 368 assert_equal NUM_REV, @repository.changesets.count
369 369 # with limit
370 370 changesets = @repository.latest_changesets('', 'master', 2)
371 371 assert_equal 2, changesets.size
372 372
373 373 # with path
374 374 changesets = @repository.latest_changesets('images', 'master')
375 375 assert_equal [
376 376 'deff712f05a90d96edbd70facc47d944be5897e3',
377 377 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
378 378 '7234cb2750b63f47bff735edc50a1c0a433c2518',
379 379 ], changesets.collect(&:revision)
380 380
381 381 changesets = @repository.latest_changesets('README', nil)
382 382 assert_equal [
383 383 '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf',
384 384 '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8',
385 385 '713f4944648826f558cf548222f813dabe7cbb04',
386 386 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
387 387 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
388 388 '7234cb2750b63f47bff735edc50a1c0a433c2518',
389 389 ], changesets.collect(&:revision)
390 390
391 391 # with path, revision and limit
392 392 changesets = @repository.latest_changesets('images', '899a15dba')
393 393 assert_equal [
394 394 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
395 395 '7234cb2750b63f47bff735edc50a1c0a433c2518',
396 396 ], changesets.collect(&:revision)
397 397
398 398 changesets = @repository.latest_changesets('images', '899a15dba', 1)
399 399 assert_equal [
400 400 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
401 401 ], changesets.collect(&:revision)
402 402
403 403 changesets = @repository.latest_changesets('README', '899a15dba')
404 404 assert_equal [
405 405 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
406 406 '7234cb2750b63f47bff735edc50a1c0a433c2518',
407 407 ], changesets.collect(&:revision)
408 408
409 409 changesets = @repository.latest_changesets('README', '899a15dba', 1)
410 410 assert_equal [
411 411 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
412 412 ], changesets.collect(&:revision)
413 413
414 414 # with path, tag and limit
415 415 changesets = @repository.latest_changesets('images', 'tag01.annotated')
416 416 assert_equal [
417 417 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
418 418 '7234cb2750b63f47bff735edc50a1c0a433c2518',
419 419 ], changesets.collect(&:revision)
420 420
421 421 changesets = @repository.latest_changesets('images', 'tag01.annotated', 1)
422 422 assert_equal [
423 423 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
424 424 ], changesets.collect(&:revision)
425 425
426 426 changesets = @repository.latest_changesets('README', 'tag01.annotated')
427 427 assert_equal [
428 428 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
429 429 '7234cb2750b63f47bff735edc50a1c0a433c2518',
430 430 ], changesets.collect(&:revision)
431 431
432 432 changesets = @repository.latest_changesets('README', 'tag01.annotated', 1)
433 433 assert_equal [
434 434 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
435 435 ], changesets.collect(&:revision)
436 436
437 437 # with path, branch and limit
438 438 changesets = @repository.latest_changesets('images', 'test_branch')
439 439 assert_equal [
440 440 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
441 441 '7234cb2750b63f47bff735edc50a1c0a433c2518',
442 442 ], changesets.collect(&:revision)
443 443
444 444 changesets = @repository.latest_changesets('images', 'test_branch', 1)
445 445 assert_equal [
446 446 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
447 447 ], changesets.collect(&:revision)
448 448
449 449 changesets = @repository.latest_changesets('README', 'test_branch')
450 450 assert_equal [
451 451 '713f4944648826f558cf548222f813dabe7cbb04',
452 452 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
453 453 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
454 454 '7234cb2750b63f47bff735edc50a1c0a433c2518',
455 455 ], changesets.collect(&:revision)
456 456
457 457 changesets = @repository.latest_changesets('README', 'test_branch', 2)
458 458 assert_equal [
459 459 '713f4944648826f558cf548222f813dabe7cbb04',
460 460 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
461 461 ], changesets.collect(&:revision)
462 462
463 463 if WINDOWS_PASS
464 464 puts WINDOWS_SKIP_STR
465 465 elsif JRUBY_SKIP
466 466 puts JRUBY_SKIP_STR
467 467 else
468 468 # latin-1 encoding path
469 469 changesets = @repository.latest_changesets(
470 470 "latin-1-dir/test-#{CHAR_1_HEX}-2.txt", '64f1f3e89')
471 471 assert_equal [
472 472 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
473 473 '4fc55c43bf3d3dc2efb66145365ddc17639ce81e',
474 474 ], changesets.collect(&:revision)
475 475
476 476 changesets = @repository.latest_changesets(
477 477 "latin-1-dir/test-#{CHAR_1_HEX}-2.txt", '64f1f3e89', 1)
478 478 assert_equal [
479 479 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
480 480 ], changesets.collect(&:revision)
481 481 end
482 482 end
483 483
484 484 def test_latest_changesets_latin_1_dir
485 485 if WINDOWS_PASS
486 486 puts WINDOWS_SKIP_STR
487 487 elsif JRUBY_SKIP
488 488 puts JRUBY_SKIP_STR
489 489 else
490 490 assert_equal 0, @repository.changesets.count
491 491 @repository.fetch_changesets
492 492 @project.reload
493 493 assert_equal NUM_REV, @repository.changesets.count
494 494 changesets = @repository.latest_changesets(
495 495 "latin-1-dir/test-#{CHAR_1_HEX}-subdir", '1ca7f5ed')
496 496 assert_equal [
497 497 '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127',
498 498 ], changesets.collect(&:revision)
499 499 end
500 500 end
501 501
502 502 def test_find_changeset_by_name
503 503 assert_equal 0, @repository.changesets.count
504 504 @repository.fetch_changesets
505 505 @project.reload
506 506 assert_equal NUM_REV, @repository.changesets.count
507 507 ['7234cb2750b63f47bff735edc50a1c0a433c2518', '7234cb2750b'].each do |r|
508 508 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518',
509 509 @repository.find_changeset_by_name(r).revision
510 510 end
511 511 end
512 512
513 513 def test_find_changeset_by_empty_name
514 514 assert_equal 0, @repository.changesets.count
515 515 @repository.fetch_changesets
516 516 @project.reload
517 517 assert_equal NUM_REV, @repository.changesets.count
518 518 ['', ' ', nil].each do |r|
519 519 assert_nil @repository.find_changeset_by_name(r)
520 520 end
521 521 end
522 522
523 523 def test_identifier
524 524 assert_equal 0, @repository.changesets.count
525 525 @repository.fetch_changesets
526 526 @project.reload
527 527 assert_equal NUM_REV, @repository.changesets.count
528 528 c = @repository.changesets.find_by_revision(
529 529 '7234cb2750b63f47bff735edc50a1c0a433c2518')
530 530 assert_equal c.scmid, c.identifier
531 531 end
532 532
533 533 def test_format_identifier
534 534 assert_equal 0, @repository.changesets.count
535 535 @repository.fetch_changesets
536 536 @project.reload
537 537 assert_equal NUM_REV, @repository.changesets.count
538 538 c = @repository.changesets.find_by_revision(
539 539 '7234cb2750b63f47bff735edc50a1c0a433c2518')
540 540 assert_equal '7234cb27', c.format_identifier
541 541 end
542 542
543 543 def test_activities
544 544 c = Changeset.new(:repository => @repository,
545 545 :committed_on => Time.now,
546 546 :revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
547 547 :scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
548 548 :comments => 'test')
549 549 assert c.event_title.include?('abc7234c:')
550 550 assert_equal 'abc7234cb2750b63f47bff735edc50a1c0a433c2', c.event_url[:rev]
551 551 end
552 552
553 553 def test_log_utf8
554 554 assert_equal 0, @repository.changesets.count
555 555 @repository.fetch_changesets
556 556 @project.reload
557 557 assert_equal NUM_REV, @repository.changesets.count
558 558 c = @repository.changesets.find_by_revision(
559 559 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b')
560 560 assert_equal "#{FELIX_HEX} <felix@fachschaften.org>", c.committer
561 561 end
562 562
563 563 def test_previous
564 564 assert_equal 0, @repository.changesets.count
565 565 @repository.fetch_changesets
566 566 @project.reload
567 567 assert_equal NUM_REV, @repository.changesets.count
568 568 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
569 569 changeset = @repository.find_changeset_by_name(r1)
570 570 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
571 571 assert_equal @repository.find_changeset_by_name(r2), changeset.previous
572 572 end
573 573 end
574 574 end
575 575
576 576 def test_previous_nil
577 577 assert_equal 0, @repository.changesets.count
578 578 @repository.fetch_changesets
579 579 @project.reload
580 580 assert_equal NUM_REV, @repository.changesets.count
581 581 %w|7234cb2750b63f47bff735edc50a1c0a433c2518 7234cb275|.each do |r1|
582 582 changeset = @repository.find_changeset_by_name(r1)
583 583 assert_nil changeset.previous
584 584 end
585 585 end
586 586
587 587 def test_next
588 588 assert_equal 0, @repository.changesets.count
589 589 @repository.fetch_changesets
590 590 @project.reload
591 591 assert_equal NUM_REV, @repository.changesets.count
592 592 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
593 593 changeset = @repository.find_changeset_by_name(r2)
594 594 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
595 595 assert_equal @repository.find_changeset_by_name(r1), changeset.next
596 596 end
597 597 end
598 598 end
599 599
600 600 def test_next_nil
601 601 assert_equal 0, @repository.changesets.count
602 602 @repository.fetch_changesets
603 603 @project.reload
604 604 assert_equal NUM_REV, @repository.changesets.count
605 605 %w|2a682156a3b6e77a8bf9cd4590e8db757f3c6c78 2a682156a3b6e77a|.each do |r1|
606 606 changeset = @repository.find_changeset_by_name(r1)
607 607 assert_nil changeset.next
608 608 end
609 609 end
610 610 else
611 611 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
612 612 def test_fake; assert true end
613 613 end
614 614 end
@@ -1,1240 +1,1240
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class UserTest < ActiveSupport::TestCase
21 21 fixtures :users, :email_addresses, :members, :projects, :roles, :member_roles, :auth_sources,
22 22 :trackers, :issue_statuses,
23 23 :projects_trackers,
24 24 :watchers,
25 25 :issue_categories, :enumerations, :issues,
26 26 :journals, :journal_details,
27 27 :groups_users,
28 28 :enabled_modules,
29 29 :tokens
30 30
31 31 include Redmine::I18n
32 32
33 33 def setup
34 34 @admin = User.find(1)
35 35 @jsmith = User.find(2)
36 36 @dlopper = User.find(3)
37 37 end
38 38
39 39 def test_sorted_scope_should_sort_user_by_display_name
40 40 # Use .active to ignore anonymous with localized display name
41 41 assert_equal User.active.map(&:name).map(&:downcase).sort,
42 42 User.active.sorted.map(&:name).map(&:downcase)
43 43 end
44 44
45 45 def test_generate
46 46 User.generate!(:firstname => 'Testing connection')
47 47 User.generate!(:firstname => 'Testing connection')
48 48 assert_equal 2, User.where(:firstname => 'Testing connection').count
49 49 end
50 50
51 51 def test_truth
52 52 assert_kind_of User, @jsmith
53 53 end
54 54
55 55 def test_should_validate_status
56 56 user = User.new
57 57 user.status = 0
58 58
59 59 assert !user.save
60 60 assert_include I18n.translate('activerecord.errors.messages.invalid'), user.errors[:status]
61 61 end
62 62
63 63 def test_mail_should_be_stripped
64 64 u = User.new
65 65 u.mail = " foo@bar.com "
66 66 assert_equal "foo@bar.com", u.mail
67 67 end
68 68
69 69 def test_should_create_email_address
70 70 u = User.new(:firstname => "new", :lastname => "user")
71 71 u.login = "create_email_address"
72 72 u.mail = "defaultemail@somenet.foo"
73 73 assert u.save
74 74 u.reload
75 75 assert u.email_address
76 76 assert_equal "defaultemail@somenet.foo", u.email_address.address
77 77 assert_equal true, u.email_address.is_default
78 78 assert_equal true, u.email_address.notify
79 79 end
80 80
81 81 def test_should_not_create_user_without_mail
82 82 set_language_if_valid 'en'
83 83 u = User.new(:firstname => "new", :lastname => "user")
84 84 u.login = "user_without_mail"
85 85 assert !u.save
86 86 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
87 87 end
88 88
89 89 def test_should_not_create_user_with_blank_mail
90 90 set_language_if_valid 'en'
91 91 u = User.new(:firstname => "new", :lastname => "user")
92 92 u.login = "user_with_blank_mail"
93 93 u.mail = ''
94 94 assert !u.save
95 95 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
96 96 end
97 97
98 98 def test_should_not_update_user_with_blank_mail
99 99 set_language_if_valid 'en'
100 100 u = User.find(2)
101 101 u.mail = ''
102 102 assert !u.save
103 103 assert_equal ["Email #{I18n.translate('activerecord.errors.messages.blank')}"], u.errors.full_messages
104 104 end
105 105
106 106 def test_login_length_validation
107 107 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
108 108 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
109 109 assert !user.valid?
110 110
111 111 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
112 112 assert user.valid?
113 113 assert user.save
114 114 end
115 115
116 116 def test_generate_password_should_respect_minimum_password_length
117 117 with_settings :password_min_length => 15 do
118 118 user = User.generate!(:generate_password => true)
119 119 assert user.password.length >= 15
120 120 end
121 121 end
122 122
123 123 def test_generate_password_should_not_generate_password_with_less_than_10_characters
124 124 with_settings :password_min_length => 4 do
125 125 user = User.generate!(:generate_password => true)
126 126 assert user.password.length >= 10
127 127 end
128 128 end
129 129
130 130 def test_generate_password_on_create_should_set_password
131 131 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
132 132 user.login = "newuser"
133 133 user.generate_password = true
134 134 assert user.save
135 135
136 136 password = user.password
137 137 assert user.check_password?(password)
138 138 end
139 139
140 140 def test_generate_password_on_update_should_update_password
141 141 user = User.find(2)
142 142 hash = user.hashed_password
143 143 user.generate_password = true
144 144 assert user.save
145 145
146 146 password = user.password
147 147 assert user.check_password?(password)
148 148 assert_not_equal hash, user.reload.hashed_password
149 149 end
150 150
151 151 def test_create
152 152 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
153 153
154 154 user.login = "jsmith"
155 155 user.password, user.password_confirmation = "password", "password"
156 156 # login uniqueness
157 157 assert !user.save
158 158 assert_equal 1, user.errors.count
159 159
160 160 user.login = "newuser"
161 161 user.password, user.password_confirmation = "password", "pass"
162 162 # password confirmation
163 163 assert !user.save
164 164 assert_equal 1, user.errors.count
165 165
166 166 user.password, user.password_confirmation = "password", "password"
167 167 assert user.save
168 168 end
169 169
170 170 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
171 171 @user1 = User.generate!
172 172 assert_equal 'only_my_events', @user1.mail_notification
173 173 with_settings :default_notification_option => 'all' do
174 174 @user2 = User.generate!
175 175 assert_equal 'all', @user2.mail_notification
176 176 end
177 177 end
178 178
179 179 def test_user_login_should_be_case_insensitive
180 180 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
181 181 u.login = 'newuser'
182 182 u.password, u.password_confirmation = "password", "password"
183 183 assert u.save
184 184 u = User.new(:firstname => "Similar", :lastname => "User",
185 185 :mail => "similaruser@somenet.foo")
186 186 u.login = 'NewUser'
187 187 u.password, u.password_confirmation = "password", "password"
188 188 assert !u.save
189 189 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
190 190 end
191 191
192 192 def test_mail_uniqueness_should_not_be_case_sensitive
193 193 set_language_if_valid 'en'
194 194 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
195 195 u.login = 'newuser1'
196 196 u.password, u.password_confirmation = "password", "password"
197 197 assert u.save
198 198
199 199 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
200 200 u.login = 'newuser2'
201 201 u.password, u.password_confirmation = "password", "password"
202 202 assert !u.save
203 203 assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages
204 204 end
205 205
206 206 def test_update
207 207 assert_equal "admin", @admin.login
208 208 @admin.login = "john"
209 209 assert @admin.save, @admin.errors.full_messages.join("; ")
210 210 @admin.reload
211 211 assert_equal "john", @admin.login
212 212 end
213 213
214 214 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
215 215 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
216 216 u1.login = 'newuser1'
217 217 assert u1.save
218 218
219 219 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
220 220 u2.login = 'newuser1'
221 221 assert u2.save(:validate => false)
222 222
223 223 user = User.find(u2.id)
224 224 user.firstname = "firstname"
225 225 assert user.save, "Save failed"
226 226 end
227 227
228 228 def test_destroy_should_delete_members_and_roles
229 229 members = Member.where(:user_id => 2)
230 230 ms = members.count
231 231 rs = members.collect(&:roles).flatten.size
232 232 assert ms > 0
233 233 assert rs > 0
234 234 assert_difference 'Member.count', - ms do
235 235 assert_difference 'MemberRole.count', - rs do
236 236 User.find(2).destroy
237 237 end
238 238 end
239 239 assert_nil User.find_by_id(2)
240 240 assert_equal 0, Member.where(:user_id => 2).count
241 241 end
242 242
243 243 def test_destroy_should_update_attachments
244 244 attachment = Attachment.create!(:container => Project.find(1),
245 245 :file => uploaded_test_file("testfile.txt", "text/plain"),
246 246 :author_id => 2)
247 247
248 248 User.find(2).destroy
249 249 assert_nil User.find_by_id(2)
250 250 assert_equal User.anonymous, attachment.reload.author
251 251 end
252 252
253 253 def test_destroy_should_update_comments
254 254 comment = Comment.create!(
255 255 :commented => News.create!(:project_id => 1,
256 256 :author_id => 1, :title => 'foo', :description => 'foo'),
257 257 :author => User.find(2),
258 258 :comments => 'foo'
259 259 )
260 260
261 261 User.find(2).destroy
262 262 assert_nil User.find_by_id(2)
263 263 assert_equal User.anonymous, comment.reload.author
264 264 end
265 265
266 266 def test_destroy_should_update_issues
267 267 issue = Issue.create!(:project_id => 1, :author_id => 2,
268 268 :tracker_id => 1, :subject => 'foo')
269 269
270 270 User.find(2).destroy
271 271 assert_nil User.find_by_id(2)
272 272 assert_equal User.anonymous, issue.reload.author
273 273 end
274 274
275 275 def test_destroy_should_unassign_issues
276 276 issue = Issue.create!(:project_id => 1, :author_id => 1,
277 277 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
278 278
279 279 User.find(2).destroy
280 280 assert_nil User.find_by_id(2)
281 281 assert_nil issue.reload.assigned_to
282 282 end
283 283
284 284 def test_destroy_should_update_journals
285 285 issue = Issue.create!(:project_id => 1, :author_id => 2,
286 286 :tracker_id => 1, :subject => 'foo')
287 287 issue.init_journal(User.find(2), "update")
288 288 issue.save!
289 289
290 290 User.find(2).destroy
291 291 assert_nil User.find_by_id(2)
292 292 assert_equal User.anonymous, issue.journals.first.reload.user
293 293 end
294 294
295 295 def test_destroy_should_update_journal_details_old_value
296 296 issue = Issue.create!(:project_id => 1, :author_id => 1,
297 297 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
298 298 issue.init_journal(User.find(1), "update")
299 299 issue.assigned_to_id = nil
300 300 assert_difference 'JournalDetail.count' do
301 301 issue.save!
302 302 end
303 303 journal_detail = JournalDetail.order('id DESC').first
304 304 assert_equal '2', journal_detail.old_value
305 305
306 306 User.find(2).destroy
307 307 assert_nil User.find_by_id(2)
308 308 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
309 309 end
310 310
311 311 def test_destroy_should_update_journal_details_value
312 312 issue = Issue.create!(:project_id => 1, :author_id => 1,
313 313 :tracker_id => 1, :subject => 'foo')
314 314 issue.init_journal(User.find(1), "update")
315 315 issue.assigned_to_id = 2
316 316 assert_difference 'JournalDetail.count' do
317 317 issue.save!
318 318 end
319 319 journal_detail = JournalDetail.order('id DESC').first
320 320 assert_equal '2', journal_detail.value
321 321
322 322 User.find(2).destroy
323 323 assert_nil User.find_by_id(2)
324 324 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
325 325 end
326 326
327 327 def test_destroy_should_update_messages
328 328 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
329 329 message = Message.create!(:board_id => board.id, :author_id => 2,
330 330 :subject => 'foo', :content => 'foo')
331 331 User.find(2).destroy
332 332 assert_nil User.find_by_id(2)
333 333 assert_equal User.anonymous, message.reload.author
334 334 end
335 335
336 336 def test_destroy_should_update_news
337 337 news = News.create!(:project_id => 1, :author_id => 2,
338 338 :title => 'foo', :description => 'foo')
339 339 User.find(2).destroy
340 340 assert_nil User.find_by_id(2)
341 341 assert_equal User.anonymous, news.reload.author
342 342 end
343 343
344 344 def test_destroy_should_delete_private_queries
345 345 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PRIVATE)
346 346 query.project_id = 1
347 347 query.user_id = 2
348 348 query.save!
349 349
350 350 User.find(2).destroy
351 351 assert_nil User.find_by_id(2)
352 352 assert_nil Query.find_by_id(query.id)
353 353 end
354 354
355 355 def test_destroy_should_update_public_queries
356 356 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PUBLIC)
357 357 query.project_id = 1
358 358 query.user_id = 2
359 359 query.save!
360 360
361 361 User.find(2).destroy
362 362 assert_nil User.find_by_id(2)
363 363 assert_equal User.anonymous, query.reload.user
364 364 end
365 365
366 366 def test_destroy_should_update_time_entries
367 367 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today,
368 368 :activity => TimeEntryActivity.create!(:name => 'foo'))
369 369 entry.project_id = 1
370 370 entry.user_id = 2
371 371 entry.save!
372 372
373 373 User.find(2).destroy
374 374 assert_nil User.find_by_id(2)
375 375 assert_equal User.anonymous, entry.reload.user
376 376 end
377 377
378 378 def test_destroy_should_delete_tokens
379 379 token = Token.create!(:user_id => 2, :value => 'foo')
380 380
381 381 User.find(2).destroy
382 382 assert_nil User.find_by_id(2)
383 383 assert_nil Token.find_by_id(token.id)
384 384 end
385 385
386 386 def test_destroy_should_delete_watchers
387 387 issue = Issue.create!(:project_id => 1, :author_id => 1,
388 388 :tracker_id => 1, :subject => 'foo')
389 389 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
390 390
391 391 User.find(2).destroy
392 392 assert_nil User.find_by_id(2)
393 393 assert_nil Watcher.find_by_id(watcher.id)
394 394 end
395 395
396 396 def test_destroy_should_update_wiki_contents
397 397 wiki_content = WikiContent.create!(
398 398 :text => 'foo',
399 399 :author_id => 2,
400 400 :page => WikiPage.create!(:title => 'Foo',
401 401 :wiki => Wiki.create!(:project_id => 3,
402 402 :start_page => 'Start'))
403 403 )
404 404 wiki_content.text = 'bar'
405 405 assert_difference 'WikiContent::Version.count' do
406 406 wiki_content.save!
407 407 end
408 408
409 409 User.find(2).destroy
410 410 assert_nil User.find_by_id(2)
411 411 assert_equal User.anonymous, wiki_content.reload.author
412 412 wiki_content.versions.each do |version|
413 413 assert_equal User.anonymous, version.reload.author
414 414 end
415 415 end
416 416
417 417 def test_destroy_should_nullify_issue_categories
418 418 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
419 419
420 420 User.find(2).destroy
421 421 assert_nil User.find_by_id(2)
422 422 assert_nil category.reload.assigned_to_id
423 423 end
424 424
425 425 def test_destroy_should_nullify_changesets
426 426 changeset = Changeset.create!(
427 427 :repository => Repository::Subversion.create!(
428 428 :project_id => 1,
429 429 :url => 'file:///tmp',
430 430 :identifier => 'tmp'
431 431 ),
432 432 :revision => '12',
433 433 :committed_on => Time.now,
434 434 :committer => 'jsmith'
435 435 )
436 436 assert_equal 2, changeset.user_id
437 437
438 438 User.find(2).destroy
439 439 assert_nil User.find_by_id(2)
440 440 assert_nil changeset.reload.user_id
441 441 end
442 442
443 443 def test_anonymous_user_should_not_be_destroyable
444 444 assert_no_difference 'User.count' do
445 445 assert_equal false, User.anonymous.destroy
446 446 end
447 447 end
448 448
449 449 def test_password_change_should_destroy_tokens
450 450 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
451 451 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
452 452
453 453 user = User.find(2)
454 454 user.password, user.password_confirmation = "a new password", "a new password"
455 455 assert user.save
456 456
457 457 assert_nil Token.find_by_id(recovery_token.id)
458 458 assert_nil Token.find_by_id(autologin_token.id)
459 459 end
460 460
461 461 def test_mail_change_should_destroy_tokens
462 462 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
463 463 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
464 464
465 465 user = User.find(2)
466 466 user.mail = "user@somwehere.com"
467 467 assert user.save
468 468
469 469 assert_nil Token.find_by_id(recovery_token.id)
470 470 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
471 471 end
472 472
473 473 def test_change_on_other_fields_should_not_destroy_tokens
474 474 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
475 475 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
476 476
477 477 user = User.find(2)
478 478 user.firstname = "Bobby"
479 479 assert user.save
480 480
481 481 assert_equal recovery_token, Token.find_by_id(recovery_token.id)
482 482 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
483 483 end
484 484
485 485 def test_validate_login_presence
486 486 @admin.login = ""
487 487 assert !@admin.save
488 488 assert_equal 1, @admin.errors.count
489 489 end
490 490
491 491 def test_validate_mail_notification_inclusion
492 492 u = User.new
493 493 u.mail_notification = 'foo'
494 494 u.save
495 495 assert_not_equal [], u.errors[:mail_notification]
496 496 end
497 497
498 498 def test_password
499 499 user = User.try_to_login("admin", "admin")
500 500 assert_kind_of User, user
501 501 assert_equal "admin", user.login
502 502 user.password = "hello123"
503 503 assert user.save
504 504
505 505 user = User.try_to_login("admin", "hello123")
506 506 assert_kind_of User, user
507 507 assert_equal "admin", user.login
508 508 end
509 509
510 510 def test_validate_password_length
511 511 with_settings :password_min_length => '100' do
512 512 user = User.new(:firstname => "new100",
513 513 :lastname => "user100", :mail => "newuser100@somenet.foo")
514 514 user.login = "newuser100"
515 515 user.password, user.password_confirmation = "password100", "password100"
516 516 assert !user.save
517 517 assert_equal 1, user.errors.count
518 518 end
519 519 end
520 520
521 521 def test_name_format
522 522 assert_equal 'John S.', @jsmith.name(:firstname_lastinitial)
523 523 assert_equal 'Smith, John', @jsmith.name(:lastname_comma_firstname)
524 524 assert_equal 'J. Smith', @jsmith.name(:firstinitial_lastname)
525 525 assert_equal 'J.-P. Lang', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').name(:firstinitial_lastname)
526 526 end
527 527
528 528 def test_name_should_use_setting_as_default_format
529 529 with_settings :user_format => :firstname_lastname do
530 530 assert_equal 'John Smith', @jsmith.reload.name
531 531 end
532 532 with_settings :user_format => :username do
533 533 assert_equal 'jsmith', @jsmith.reload.name
534 534 end
535 535 with_settings :user_format => :lastname do
536 536 assert_equal 'Smith', @jsmith.reload.name
537 537 end
538 538 end
539 539
540 540 def test_today_should_return_the_day_according_to_user_time_zone
541 541 preference = User.find(1).pref
542 542 date = Date.new(2012, 05, 15)
543 543 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
544 544 Date.stubs(:today).returns(date)
545 545 Time.stubs(:now).returns(time)
546 546
547 547 preference.update_attribute :time_zone, 'Baku' # UTC+4
548 548 assert_equal '2012-05-16', User.find(1).today.to_s
549 549
550 550 preference.update_attribute :time_zone, 'La Paz' # UTC-4
551 551 assert_equal '2012-05-15', User.find(1).today.to_s
552 552
553 553 preference.update_attribute :time_zone, ''
554 554 assert_equal '2012-05-15', User.find(1).today.to_s
555 555 end
556 556
557 557 def test_time_to_date_should_return_the_date_according_to_user_time_zone
558 558 preference = User.find(1).pref
559 559 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
560 560
561 561 preference.update_attribute :time_zone, 'Baku' # UTC+4
562 562 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
563 563
564 564 preference.update_attribute :time_zone, 'La Paz' # UTC-4
565 565 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
566 566
567 567 preference.update_attribute :time_zone, ''
568 568 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
569 569 end
570 570
571 571 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
572 572 with_settings :user_format => 'lastname_comma_firstname' do
573 573 assert_equal ['users.lastname', 'users.firstname', 'users.id'],
574 574 User.fields_for_order_statement
575 575 end
576 576 end
577 577
578 578 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
579 579 with_settings :user_format => 'lastname_firstname' do
580 580 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'],
581 581 User.fields_for_order_statement('authors')
582 582 end
583 583 end
584 584
585 585 def test_fields_for_order_statement_with_blank_format_should_return_default
586 586 with_settings :user_format => '' do
587 587 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
588 588 User.fields_for_order_statement
589 589 end
590 590 end
591 591
592 592 def test_fields_for_order_statement_with_invalid_format_should_return_default
593 593 with_settings :user_format => 'foo' do
594 594 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
595 595 User.fields_for_order_statement
596 596 end
597 597 end
598 598
599 599 test ".try_to_login with good credentials should return the user" do
600 600 user = User.try_to_login("admin", "admin")
601 601 assert_kind_of User, user
602 602 assert_equal "admin", user.login
603 603 end
604 604
605 605 test ".try_to_login with wrong credentials should return nil" do
606 606 assert_nil User.try_to_login("admin", "foo")
607 607 end
608 608
609 609 def test_try_to_login_with_locked_user_should_return_nil
610 610 @jsmith.status = User::STATUS_LOCKED
611 611 @jsmith.save!
612 612
613 613 user = User.try_to_login("jsmith", "jsmith")
614 assert_equal nil, user
614 assert_nil user
615 615 end
616 616
617 617 def test_try_to_login_with_locked_user_and_not_active_only_should_return_user
618 618 @jsmith.status = User::STATUS_LOCKED
619 619 @jsmith.save!
620 620
621 621 user = User.try_to_login("jsmith", "jsmith", false)
622 622 assert_equal @jsmith, user
623 623 end
624 624
625 625 test ".try_to_login should fall-back to case-insensitive if user login is not found as-typed" do
626 626 user = User.try_to_login("AdMin", "admin")
627 627 assert_kind_of User, user
628 628 assert_equal "admin", user.login
629 629 end
630 630
631 631 test ".try_to_login should select the exact matching user first" do
632 632 case_sensitive_user = User.generate! do |user|
633 633 user.password = "admin123"
634 634 end
635 635 # bypass validations to make it appear like existing data
636 636 case_sensitive_user.update_attribute(:login, 'ADMIN')
637 637
638 638 user = User.try_to_login("ADMIN", "admin123")
639 639 assert_kind_of User, user
640 640 assert_equal "ADMIN", user.login
641 641 end
642 642
643 643 if ldap_configured?
644 644 test "#try_to_login using LDAP with failed connection to the LDAP server" do
645 645 auth_source = AuthSourceLdap.find(1)
646 646 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
647 647
648 assert_equal nil, User.try_to_login('edavis', 'wrong')
648 assert_nil User.try_to_login('edavis', 'wrong')
649 649 end
650 650
651 651 test "#try_to_login using LDAP" do
652 assert_equal nil, User.try_to_login('edavis', 'wrong')
652 assert_nil User.try_to_login('edavis', 'wrong')
653 653 end
654 654
655 655 test "#try_to_login using LDAP binding with user's account" do
656 656 auth_source = AuthSourceLdap.find(1)
657 657 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
658 658 auth_source.account_password = ''
659 659 auth_source.save!
660 660
661 661 ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
662 662 ldap_user.login = 'example1'
663 663 ldap_user.save!
664 664
665 665 assert_equal ldap_user, User.try_to_login('example1', '123456')
666 666 assert_nil User.try_to_login('example1', '11111')
667 667 end
668 668
669 669 test "#try_to_login using LDAP on the fly registration" do
670 670 AuthSourceLdap.find(1).update_attribute :onthefly_register, true
671 671
672 672 assert_difference('User.count') do
673 673 assert User.try_to_login('edavis', '123456')
674 674 end
675 675
676 676 assert_no_difference('User.count') do
677 677 assert User.try_to_login('edavis', '123456')
678 678 end
679 679
680 680 assert_nil User.try_to_login('example1', '11111')
681 681 end
682 682
683 683 test "#try_to_login using LDAP on the fly registration and binding with user's account" do
684 684 auth_source = AuthSourceLdap.find(1)
685 685 auth_source.update_attribute :onthefly_register, true
686 686 auth_source = AuthSourceLdap.find(1)
687 687 auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
688 688 auth_source.account_password = ''
689 689 auth_source.save!
690 690
691 691 assert_difference('User.count') do
692 692 assert User.try_to_login('example1', '123456')
693 693 end
694 694
695 695 assert_no_difference('User.count') do
696 696 assert User.try_to_login('example1', '123456')
697 697 end
698 698
699 699 assert_nil User.try_to_login('example1', '11111')
700 700 end
701 701
702 702 else
703 703 puts "Skipping LDAP tests."
704 704 end
705 705
706 706 def test_create_anonymous
707 707 AnonymousUser.delete_all
708 708 anon = User.anonymous
709 709 assert !anon.new_record?
710 710 assert_kind_of AnonymousUser, anon
711 711 end
712 712
713 713 def test_ensure_single_anonymous_user
714 714 AnonymousUser.delete_all
715 715 anon1 = User.anonymous
716 716 assert !anon1.new_record?
717 717 assert_kind_of AnonymousUser, anon1
718 718 anon2 = AnonymousUser.create(
719 719 :lastname => 'Anonymous', :firstname => '',
720 720 :login => '', :status => 0)
721 721 assert_equal 1, anon2.errors.count
722 722 end
723 723
724 724 def test_rss_key
725 725 assert_nil @jsmith.rss_token
726 726 key = @jsmith.rss_key
727 727 assert_equal 40, key.length
728 728
729 729 @jsmith.reload
730 730 assert_equal key, @jsmith.rss_key
731 731 end
732 732
733 733 def test_rss_key_should_not_be_generated_twice
734 734 assert_difference 'Token.count', 1 do
735 735 key1 = @jsmith.rss_key
736 736 key2 = @jsmith.rss_key
737 737 assert_equal key1, key2
738 738 end
739 739 end
740 740
741 741 def test_api_key_should_not_be_generated_twice
742 742 assert_difference 'Token.count', 1 do
743 743 key1 = @jsmith.api_key
744 744 key2 = @jsmith.api_key
745 745 assert_equal key1, key2
746 746 end
747 747 end
748 748
749 749 test "#api_key should generate a new one if the user doesn't have one" do
750 750 user = User.generate!(:api_token => nil)
751 751 assert_nil user.api_token
752 752
753 753 key = user.api_key
754 754 assert_equal 40, key.length
755 755 user.reload
756 756 assert_equal key, user.api_key
757 757 end
758 758
759 759 test "#api_key should return the existing api token value" do
760 760 user = User.generate!
761 761 token = Token.create!(:action => 'api')
762 762 user.api_token = token
763 763 assert user.save
764 764
765 765 assert_equal token.value, user.api_key
766 766 end
767 767
768 768 test "#find_by_api_key should return nil if no matching key is found" do
769 769 assert_nil User.find_by_api_key('zzzzzzzzz')
770 770 end
771 771
772 772 test "#find_by_api_key should return nil if the key is found for an inactive user" do
773 773 user = User.generate!
774 774 user.status = User::STATUS_LOCKED
775 775 token = Token.create!(:action => 'api')
776 776 user.api_token = token
777 777 user.save
778 778
779 779 assert_nil User.find_by_api_key(token.value)
780 780 end
781 781
782 782 test "#find_by_api_key should return the user if the key is found for an active user" do
783 783 user = User.generate!
784 784 token = Token.create!(:action => 'api')
785 785 user.api_token = token
786 786 user.save
787 787
788 788 assert_equal user, User.find_by_api_key(token.value)
789 789 end
790 790
791 791 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
792 792 user = User.find_by_login("admin")
793 793 user.password = "admin"
794 794 assert user.save(:validate => false)
795 795
796 796 assert_equal false, User.default_admin_account_changed?
797 797 end
798 798
799 799 def test_default_admin_account_changed_should_return_true_if_password_was_changed
800 800 user = User.find_by_login("admin")
801 801 user.password = "newpassword"
802 802 user.save!
803 803
804 804 assert_equal true, User.default_admin_account_changed?
805 805 end
806 806
807 807 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
808 808 user = User.find_by_login("admin")
809 809 user.password = "admin"
810 810 user.status = User::STATUS_LOCKED
811 811 assert user.save(:validate => false)
812 812
813 813 assert_equal true, User.default_admin_account_changed?
814 814 end
815 815
816 816 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
817 817 user = User.find_by_login("admin")
818 818 user.destroy
819 819
820 820 assert_equal true, User.default_admin_account_changed?
821 821 end
822 822
823 823 def test_membership_with_project_should_return_membership
824 824 project = Project.find(1)
825 825
826 826 membership = @jsmith.membership(project)
827 827 assert_kind_of Member, membership
828 828 assert_equal @jsmith, membership.user
829 829 assert_equal project, membership.project
830 830 end
831 831
832 832 def test_membership_with_project_id_should_return_membership
833 833 project = Project.find(1)
834 834
835 835 membership = @jsmith.membership(1)
836 836 assert_kind_of Member, membership
837 837 assert_equal @jsmith, membership.user
838 838 assert_equal project, membership.project
839 839 end
840 840
841 841 def test_membership_for_non_member_should_return_nil
842 842 project = Project.find(1)
843 843
844 844 user = User.generate!
845 845 membership = user.membership(1)
846 846 assert_nil membership
847 847 end
848 848
849 849 def test_roles_for_project_with_member_on_public_project_should_return_roles_and_non_member
850 850 roles = @jsmith.roles_for_project(Project.find(1))
851 851 assert_kind_of Role, roles.first
852 852 assert_equal ["Manager"], roles.map(&:name)
853 853 end
854 854
855 855 def test_roles_for_project_with_member_on_private_project_should_return_roles
856 856 Project.find(1).update_attribute :is_public, false
857 857
858 858 roles = @jsmith.roles_for_project(Project.find(1))
859 859 assert_kind_of Role, roles.first
860 860 assert_equal ["Manager"], roles.map(&:name)
861 861 end
862 862
863 863 def test_roles_for_project_with_non_member_with_public_project_should_return_non_member
864 864 set_language_if_valid 'en'
865 865 roles = User.find(8).roles_for_project(Project.find(1))
866 866 assert_equal ["Non member"], roles.map(&:name)
867 867 end
868 868
869 869 def test_roles_for_project_with_non_member_with_public_project_and_override_should_return_override_roles
870 870 project = Project.find(1)
871 871 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
872 872 roles = User.find(8).roles_for_project(project)
873 873 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
874 874 end
875 875
876 876 def test_roles_for_project_with_non_member_with_private_project_should_return_no_roles
877 877 Project.find(1).update_attribute :is_public, false
878 878
879 879 roles = User.find(8).roles_for_project(Project.find(1))
880 880 assert_equal [], roles.map(&:name)
881 881 end
882 882
883 883 def test_roles_for_project_with_non_member_with_private_project_and_override_should_return_no_roles
884 884 project = Project.find(1)
885 885 project.update_attribute :is_public, false
886 886 Member.create!(:project => project, :principal => Group.non_member, :role_ids => [1, 2])
887 887 roles = User.find(8).roles_for_project(project)
888 888 assert_equal [], roles.map(&:name).sort
889 889 end
890 890
891 891 def test_roles_for_project_with_anonymous_with_public_project_should_return_anonymous
892 892 set_language_if_valid 'en'
893 893 roles = User.anonymous.roles_for_project(Project.find(1))
894 894 assert_equal ["Anonymous"], roles.map(&:name)
895 895 end
896 896
897 897 def test_roles_for_project_with_anonymous_with_public_project_and_override_should_return_override_roles
898 898 project = Project.find(1)
899 899 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
900 900 roles = User.anonymous.roles_for_project(project)
901 901 assert_equal ["Developer", "Manager"], roles.map(&:name).sort
902 902 end
903 903
904 904 def test_roles_for_project_with_anonymous_with_private_project_should_return_no_roles
905 905 Project.find(1).update_attribute :is_public, false
906 906
907 907 roles = User.anonymous.roles_for_project(Project.find(1))
908 908 assert_equal [], roles.map(&:name)
909 909 end
910 910
911 911 def test_roles_for_project_with_anonymous_with_private_project_and_override_should_return_no_roles
912 912 project = Project.find(1)
913 913 project.update_attribute :is_public, false
914 914 Member.create!(:project => project, :principal => Group.anonymous, :role_ids => [1, 2])
915 915 roles = User.anonymous.roles_for_project(project)
916 916 assert_equal [], roles.map(&:name).sort
917 917 end
918 918
919 919 def test_roles_for_project_should_be_unique
920 920 m = Member.new(:user_id => 1, :project_id => 1)
921 921 m.member_roles.build(:role_id => 1)
922 922 m.member_roles.build(:role_id => 1)
923 923 m.save!
924 924
925 925 user = User.find(1)
926 926 project = Project.find(1)
927 927 assert_equal 1, user.roles_for_project(project).size
928 928 assert_equal [1], user.roles_for_project(project).map(&:id)
929 929 end
930 930
931 931 def test_projects_by_role_for_user_with_role
932 932 user = User.find(2)
933 933 assert_kind_of Hash, user.projects_by_role
934 934 assert_equal 2, user.projects_by_role.size
935 935 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
936 936 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
937 937 end
938 938
939 939 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
940 940 user = User.find(2)
941 941 assert_equal [], user.projects_by_role[Role.find(3)]
942 942 # should not update the hash
943 943 assert_nil user.projects_by_role.values.detect(&:blank?)
944 944 end
945 945
946 946 def test_projects_by_role_for_user_with_no_role
947 947 user = User.generate!
948 948 assert_equal({}, user.projects_by_role)
949 949 end
950 950
951 951 def test_projects_by_role_for_anonymous
952 952 assert_equal({}, User.anonymous.projects_by_role)
953 953 end
954 954
955 955 def test_valid_notification_options
956 956 # without memberships
957 957 assert_equal 5, User.find(7).valid_notification_options.size
958 958 # with memberships
959 959 assert_equal 6, User.find(2).valid_notification_options.size
960 960 end
961 961
962 962 def test_valid_notification_options_class_method
963 963 assert_equal 5, User.valid_notification_options.size
964 964 assert_equal 5, User.valid_notification_options(User.find(7)).size
965 965 assert_equal 6, User.valid_notification_options(User.find(2)).size
966 966 end
967 967
968 968 def test_notified_project_ids_setter_should_coerce_to_unique_integer_array
969 969 @jsmith.notified_project_ids = ["1", "123", "2u", "wrong", "12", 6, 12, -35, ""]
970 970 assert_equal [1, 123, 2, 12, 6], @jsmith.notified_projects_ids
971 971 end
972 972
973 973 def test_mail_notification_all
974 974 @jsmith.mail_notification = 'all'
975 975 @jsmith.notified_project_ids = []
976 976 @jsmith.save
977 977 @jsmith.reload
978 978 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
979 979 end
980 980
981 981 def test_mail_notification_selected
982 982 @jsmith.mail_notification = 'selected'
983 983 @jsmith.notified_project_ids = [1]
984 984 @jsmith.save
985 985 @jsmith.reload
986 986 assert Project.find(1).recipients.include?(@jsmith.mail)
987 987 end
988 988
989 989 def test_mail_notification_only_my_events
990 990 @jsmith.mail_notification = 'only_my_events'
991 991 @jsmith.notified_project_ids = []
992 992 @jsmith.save
993 993 @jsmith.reload
994 994 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
995 995 end
996 996
997 997 def test_comments_sorting_preference
998 998 assert !@jsmith.wants_comments_in_reverse_order?
999 999 @jsmith.pref.comments_sorting = 'asc'
1000 1000 assert !@jsmith.wants_comments_in_reverse_order?
1001 1001 @jsmith.pref.comments_sorting = 'desc'
1002 1002 assert @jsmith.wants_comments_in_reverse_order?
1003 1003 end
1004 1004
1005 1005 def test_find_by_mail_should_be_case_insensitive
1006 1006 u = User.find_by_mail('JSmith@somenet.foo')
1007 1007 assert_not_nil u
1008 1008 assert_equal 'jsmith@somenet.foo', u.mail
1009 1009 end
1010 1010
1011 1011 def test_random_password
1012 1012 u = User.new
1013 1013 u.random_password
1014 1014 assert !u.password.blank?
1015 1015 assert !u.password_confirmation.blank?
1016 1016 end
1017 1017
1018 1018 test "#change_password_allowed? should be allowed if no auth source is set" do
1019 1019 user = User.generate!
1020 1020 assert user.change_password_allowed?
1021 1021 end
1022 1022
1023 1023 test "#change_password_allowed? should delegate to the auth source" do
1024 1024 user = User.generate!
1025 1025
1026 1026 allowed_auth_source = AuthSource.generate!
1027 1027 def allowed_auth_source.allow_password_changes?; true; end
1028 1028
1029 1029 denied_auth_source = AuthSource.generate!
1030 1030 def denied_auth_source.allow_password_changes?; false; end
1031 1031
1032 1032 assert user.change_password_allowed?
1033 1033
1034 1034 user.auth_source = allowed_auth_source
1035 1035 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
1036 1036
1037 1037 user.auth_source = denied_auth_source
1038 1038 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
1039 1039 end
1040 1040
1041 1041 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
1042 1042 with_settings :unsubscribe => '1' do
1043 1043 assert_equal true, User.find(2).own_account_deletable?
1044 1044 end
1045 1045 end
1046 1046
1047 1047 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
1048 1048 with_settings :unsubscribe => '0' do
1049 1049 assert_equal false, User.find(2).own_account_deletable?
1050 1050 end
1051 1051 end
1052 1052
1053 1053 def test_own_account_deletable_should_be_false_for_a_single_admin
1054 1054 User.where(["admin = ? AND id <> ?", true, 1]).delete_all
1055 1055
1056 1056 with_settings :unsubscribe => '1' do
1057 1057 assert_equal false, User.find(1).own_account_deletable?
1058 1058 end
1059 1059 end
1060 1060
1061 1061 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
1062 1062 User.generate! do |user|
1063 1063 user.admin = true
1064 1064 end
1065 1065
1066 1066 with_settings :unsubscribe => '1' do
1067 1067 assert_equal true, User.find(1).own_account_deletable?
1068 1068 end
1069 1069 end
1070 1070
1071 1071 test "#allowed_to? for archived project should return false" do
1072 1072 project = Project.find(1)
1073 1073 project.archive
1074 1074 project.reload
1075 1075 assert_equal false, @admin.allowed_to?(:view_issues, project)
1076 1076 end
1077 1077
1078 1078 test "#allowed_to? for closed project should return true for read actions" do
1079 1079 project = Project.find(1)
1080 1080 project.close
1081 1081 project.reload
1082 1082 assert_equal false, @admin.allowed_to?(:edit_project, project)
1083 1083 assert_equal true, @admin.allowed_to?(:view_project, project)
1084 1084 end
1085 1085
1086 1086 test "#allowed_to? for project with module disabled should return false" do
1087 1087 project = Project.find(1)
1088 1088 project.enabled_module_names = ["issue_tracking"]
1089 1089 assert_equal true, @admin.allowed_to?(:add_issues, project)
1090 1090 assert_equal false, @admin.allowed_to?(:view_wiki_pages, project)
1091 1091 end
1092 1092
1093 1093 test "#allowed_to? for admin users should return true" do
1094 1094 project = Project.find(1)
1095 1095 assert ! @admin.member_of?(project)
1096 1096 %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
1097 1097 assert_equal true, @admin.allowed_to?(p.to_sym, project)
1098 1098 end
1099 1099 end
1100 1100
1101 1101 test "#allowed_to? for normal users" do
1102 1102 project = Project.find(1)
1103 1103 assert_equal true, @jsmith.allowed_to?(:delete_messages, project) #Manager
1104 1104 assert_equal false, @dlopper.allowed_to?(:delete_messages, project) #Developper
1105 1105 end
1106 1106
1107 1107 test "#allowed_to? with empty array should return false" do
1108 1108 assert_equal false, @admin.allowed_to?(:view_project, [])
1109 1109 end
1110 1110
1111 1111 test "#allowed_to? with multiple projects" do
1112 1112 assert_equal true, @admin.allowed_to?(:view_project, Project.all.to_a)
1113 1113 assert_equal false, @dlopper.allowed_to?(:view_project, Project.all.to_a) #cannot see Project(2)
1114 1114 assert_equal true, @jsmith.allowed_to?(:edit_issues, @jsmith.projects.to_a) #Manager or Developer everywhere
1115 1115 assert_equal false, @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects.to_a) #Dev cannot delete_issue_watchers
1116 1116 end
1117 1117
1118 1118 test "#allowed_to? with with options[:global] should return true if user has one role with the permission" do
1119 1119 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1120 1120 @anonymous = User.find(6)
1121 1121 assert_equal true, @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
1122 1122 assert_equal false, @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
1123 1123 assert_equal true, @dlopper2.allowed_to?(:add_issues, nil, :global => true)
1124 1124 assert_equal false, @anonymous.allowed_to?(:add_issues, nil, :global => true)
1125 1125 assert_equal true, @anonymous.allowed_to?(:view_issues, nil, :global => true)
1126 1126 end
1127 1127
1128 1128 # this is just a proxy method, the test only calls it to ensure it doesn't break trivially
1129 1129 test "#allowed_to_globally?" do
1130 1130 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1131 1131 @anonymous = User.find(6)
1132 1132 assert_equal true, @jsmith.allowed_to_globally?(:delete_issue_watchers)
1133 1133 assert_equal false, @dlopper2.allowed_to_globally?(:delete_issue_watchers)
1134 1134 assert_equal true, @dlopper2.allowed_to_globally?(:add_issues)
1135 1135 assert_equal false, @anonymous.allowed_to_globally?(:add_issues)
1136 1136 assert_equal true, @anonymous.allowed_to_globally?(:view_issues)
1137 1137 end
1138 1138
1139 1139 def test_notify_about_issue
1140 1140 project = Project.find(1)
1141 1141 author = User.generate!
1142 1142 assignee = User.generate!
1143 1143 Member.create!(:user => assignee, :project => project, :role_ids => [1])
1144 1144 member = User.generate!
1145 1145 Member.create!(:user => member, :project => project, :role_ids => [1])
1146 1146 issue = Issue.generate!(:project => project, :assigned_to => assignee, :author => author)
1147 1147
1148 1148 tests = {
1149 1149 author => %w(all only_my_events only_owner selected),
1150 1150 assignee => %w(all only_my_events only_assigned selected),
1151 1151 member => %w(all)
1152 1152 }
1153 1153
1154 1154 tests.each do |user, expected|
1155 1155 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1156 1156 user.mail_notification = option
1157 1157 assert_equal expected.include?(option), user.notify_about?(issue)
1158 1158 end
1159 1159 end
1160 1160 end
1161 1161
1162 1162 def test_notify_about_issue_for_previous_assignee
1163 1163 assignee = User.generate!(:mail_notification => 'only_assigned')
1164 1164 Member.create!(:user => assignee, :project_id => 1, :role_ids => [1])
1165 1165 new_assignee = User.generate!(:mail_notification => 'only_assigned')
1166 1166 Member.create!(:user => new_assignee, :project_id => 1, :role_ids => [1])
1167 1167 issue = Issue.generate!(:assigned_to => assignee)
1168 1168
1169 1169 assert assignee.notify_about?(issue)
1170 1170 assert !new_assignee.notify_about?(issue)
1171 1171
1172 1172 issue.assigned_to = new_assignee
1173 1173 assert assignee.notify_about?(issue)
1174 1174 assert new_assignee.notify_about?(issue)
1175 1175
1176 1176 issue.save!
1177 1177 assert !assignee.notify_about?(issue)
1178 1178 assert new_assignee.notify_about?(issue)
1179 1179 end
1180 1180
1181 1181 def test_notify_about_news
1182 1182 user = User.generate!
1183 1183 news = News.new
1184 1184
1185 1185 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1186 1186 user.mail_notification = option
1187 1187 assert_equal (option != 'none'), user.notify_about?(news)
1188 1188 end
1189 1189 end
1190 1190
1191 1191 def test_salt_unsalted_passwords
1192 1192 # Restore a user with an unsalted password
1193 1193 user = User.find(1)
1194 1194 user.salt = nil
1195 1195 user.hashed_password = User.hash_password("unsalted")
1196 1196 user.save!
1197 1197
1198 1198 User.salt_unsalted_passwords!
1199 1199
1200 1200 user.reload
1201 1201 # Salt added
1202 1202 assert !user.salt.blank?
1203 1203 # Password still valid
1204 1204 assert user.check_password?("unsalted")
1205 1205 assert_equal user, User.try_to_login(user.login, "unsalted")
1206 1206 end
1207 1207
1208 1208 if Object.const_defined?(:OpenID)
1209 1209 def test_setting_identity_url
1210 1210 normalized_open_id_url = 'http://example.com/'
1211 1211 u = User.new( :identity_url => 'http://example.com/' )
1212 1212 assert_equal normalized_open_id_url, u.identity_url
1213 1213 end
1214 1214
1215 1215 def test_setting_identity_url_without_trailing_slash
1216 1216 normalized_open_id_url = 'http://example.com/'
1217 1217 u = User.new( :identity_url => 'http://example.com' )
1218 1218 assert_equal normalized_open_id_url, u.identity_url
1219 1219 end
1220 1220
1221 1221 def test_setting_identity_url_without_protocol
1222 1222 normalized_open_id_url = 'http://example.com/'
1223 1223 u = User.new( :identity_url => 'example.com' )
1224 1224 assert_equal normalized_open_id_url, u.identity_url
1225 1225 end
1226 1226
1227 1227 def test_setting_blank_identity_url
1228 1228 u = User.new( :identity_url => 'example.com' )
1229 1229 u.identity_url = ''
1230 1230 assert u.identity_url.blank?
1231 1231 end
1232 1232
1233 1233 def test_setting_invalid_identity_url
1234 1234 u = User.new( :identity_url => 'this is not an openid url' )
1235 1235 assert u.identity_url.blank?
1236 1236 end
1237 1237 else
1238 1238 puts "Skipping openid tests."
1239 1239 end
1240 1240 end
@@ -1,293 +1,293
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class VersionTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :issues, :issue_statuses, :trackers,
22 22 :enumerations, :versions, :projects_trackers
23 23
24 24 def test_create
25 25 v = Version.new(:project => Project.find(1), :name => '1.1',
26 26 :effective_date => '2011-03-25')
27 27 assert v.save
28 28 assert_equal 'open', v.status
29 29 assert_equal 'none', v.sharing
30 30 end
31 31
32 32 def test_create_as_default_project_version
33 33 project = Project.find(1)
34 34 v = Version.new(:project => project, :name => '1.1',
35 35 :default_project_version => '1')
36 36 assert v.save
37 37 assert_equal v, project.reload.default_version
38 38 end
39 39
40 40 def test_create_not_as_default_project_version
41 41 project = Project.find(1)
42 42 v = Version.new(:project => project, :name => '1.1',
43 43 :default_project_version => '0')
44 44 assert v.save
45 45 assert_nil project.reload.default_version
46 46 end
47 47
48 48 def test_invalid_effective_date_validation
49 49 v = Version.new(:project => Project.find(1), :name => '1.1',
50 50 :effective_date => '99999-01-01')
51 51 assert !v.valid?
52 52 v.effective_date = '2012-11-33'
53 53 assert !v.valid?
54 54 v.effective_date = '2012-31-11'
55 55 assert !v.valid?
56 56 v.effective_date = '-2012-31-11'
57 57 assert !v.valid?
58 58 v.effective_date = 'ABC'
59 59 assert !v.valid?
60 60 assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
61 61 v.errors[:effective_date]
62 62 end
63 63
64 64 def test_progress_should_be_0_with_no_assigned_issues
65 65 project = Project.find(1)
66 66 v = Version.create!(:project => project, :name => 'Progress')
67 67 assert_equal 0, v.completed_percent
68 68 assert_equal 0, v.closed_percent
69 69 end
70 70
71 71 def test_progress_should_be_0_with_unbegun_assigned_issues
72 72 project = Project.find(1)
73 73 v = Version.create!(:project => project, :name => 'Progress')
74 74 add_issue(v)
75 75 add_issue(v, :done_ratio => 0)
76 76 assert_progress_equal 0, v.completed_percent
77 77 assert_progress_equal 0, v.closed_percent
78 78 end
79 79
80 80 def test_progress_should_be_100_with_closed_assigned_issues
81 81 project = Project.find(1)
82 82 status = IssueStatus.where(:is_closed => true).first
83 83 v = Version.create!(:project => project, :name => 'Progress')
84 84 add_issue(v, :status => status)
85 85 add_issue(v, :status => status, :done_ratio => 20)
86 86 add_issue(v, :status => status, :done_ratio => 70, :estimated_hours => 25)
87 87 add_issue(v, :status => status, :estimated_hours => 15)
88 88 assert_progress_equal 100.0, v.completed_percent
89 89 assert_progress_equal 100.0, v.closed_percent
90 90 end
91 91
92 92 def test_progress_should_consider_done_ratio_of_open_assigned_issues
93 93 project = Project.find(1)
94 94 v = Version.create!(:project => project, :name => 'Progress')
95 95 add_issue(v)
96 96 add_issue(v, :done_ratio => 20)
97 97 add_issue(v, :done_ratio => 70)
98 98 assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent
99 99 assert_progress_equal 0, v.closed_percent
100 100 end
101 101
102 102 def test_progress_should_consider_closed_issues_as_completed
103 103 project = Project.find(1)
104 104 v = Version.create!(:project => project, :name => 'Progress')
105 105 add_issue(v)
106 106 add_issue(v, :done_ratio => 20)
107 107 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
108 108 assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent
109 109 assert_progress_equal (100.0)/3, v.closed_percent
110 110 end
111 111
112 112 def test_progress_should_consider_estimated_hours_to_weight_issues
113 113 project = Project.find(1)
114 114 v = Version.create!(:project => project, :name => 'Progress')
115 115 add_issue(v, :estimated_hours => 10)
116 116 add_issue(v, :estimated_hours => 20, :done_ratio => 30)
117 117 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
118 118 add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first)
119 119 assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent
120 120 assert_progress_equal 25.0/95.0*100, v.closed_percent
121 121 end
122 122
123 123 def test_progress_should_consider_average_estimated_hours_to_weight_unestimated_issues
124 124 project = Project.find(1)
125 125 v = Version.create!(:project => project, :name => 'Progress')
126 126 add_issue(v, :done_ratio => 20)
127 127 add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
128 128 add_issue(v, :estimated_hours => 10, :done_ratio => 30)
129 129 add_issue(v, :estimated_hours => 40, :done_ratio => 10)
130 130 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent
131 131 assert_progress_equal 25.0/100.0*100, v.closed_percent
132 132 end
133 133
134 134 def test_should_sort_scheduled_then_unscheduled_versions
135 135 Version.delete_all
136 136 v4 = Version.create!(:project_id => 1, :name => 'v4')
137 137 v3 = Version.create!(:project_id => 1, :name => 'v2', :effective_date => '2012-07-14')
138 138 v2 = Version.create!(:project_id => 1, :name => 'v1')
139 139 v1 = Version.create!(:project_id => 1, :name => 'v3', :effective_date => '2012-08-02')
140 140 v5 = Version.create!(:project_id => 1, :name => 'v5', :effective_date => '2012-07-02')
141 141
142 142 assert_equal [v5, v3, v1, v2, v4], [v1, v2, v3, v4, v5].sort
143 143 assert_equal [v5, v3, v1, v2, v4], Version.sorted.to_a
144 144 end
145 145
146 146 def test_should_sort_versions_with_same_date_by_name
147 147 v1 = Version.new(:effective_date => '2014-12-03', :name => 'v2')
148 148 v2 = Version.new(:effective_date => '2014-12-03', :name => 'v1')
149 149 assert_equal [v2, v1], [v1, v2].sort
150 150 end
151 151
152 152 def test_completed_should_be_false_when_due_today
153 153 version = Version.create!(:project_id => 1, :effective_date => Date.today, :name => 'Due today')
154 154 assert_equal false, version.completed?
155 155 end
156 156
157 157 def test_completed_should_be_true_when_closed
158 158 version = Version.create!(:project_id => 1, :status => 'closed', :name => 'Closed')
159 159 assert_equal true, version.completed?
160 160 end
161 161
162 162 test "#behind_schedule? should be false if there are no issues assigned" do
163 163 version = Version.generate!(:effective_date => Date.yesterday)
164 164 assert_equal false, version.behind_schedule?
165 165 end
166 166
167 167 test "#behind_schedule? should be false if there is no effective_date" do
168 168 version = Version.generate!(:effective_date => nil)
169 169 assert_equal false, version.behind_schedule?
170 170 end
171 171
172 172 test "#behind_schedule? should be false if all of the issues are ahead of schedule" do
173 173 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
174 174 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
175 175 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
176 176 assert_equal 60, version.completed_percent
177 177 assert_equal false, version.behind_schedule?
178 178 end
179 179
180 180 test "#behind_schedule? should be true if any of the issues are behind schedule" do
181 181 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
182 182 add_issue(version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
183 183 add_issue(version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
184 184 assert_equal 40, version.completed_percent
185 185 assert_equal true, version.behind_schedule?
186 186 end
187 187
188 188 test "#behind_schedule? should be false if all of the issues are complete" do
189 189 version = Version.create!(:project_id => 1, :name => 'test', :effective_date => 7.days.from_now.to_date)
190 190 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
191 191 add_issue(version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
192 192 assert_equal 100, version.completed_percent
193 193 assert_equal false, version.behind_schedule?
194 194 end
195 195
196 196 test "#estimated_hours should return 0 with no assigned issues" do
197 197 version = Version.generate!
198 198 assert_equal 0, version.estimated_hours
199 199 end
200 200
201 201 test "#estimated_hours should return 0 with no estimated hours" do
202 202 version = Version.create!(:project_id => 1, :name => 'test')
203 203 add_issue(version)
204 204 assert_equal 0, version.estimated_hours
205 205 end
206 206
207 207 test "#estimated_hours should return return the sum of estimated hours" do
208 208 version = Version.create!(:project_id => 1, :name => 'test')
209 209 add_issue(version, :estimated_hours => 2.5)
210 210 add_issue(version, :estimated_hours => 5)
211 211 assert_equal 7.5, version.estimated_hours
212 212 end
213 213
214 214 test "#estimated_hours should return the sum of leaves estimated hours" do
215 215 version = Version.create!(:project_id => 1, :name => 'test')
216 216 parent = add_issue(version)
217 217 add_issue(version, :estimated_hours => 2.5, :parent_issue_id => parent.id)
218 218 add_issue(version, :estimated_hours => 5, :parent_issue_id => parent.id)
219 219 assert_equal 7.5, version.estimated_hours
220 220 end
221 221
222 222 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
223 223 User.current = User.find(1) # Need the admin's permissions
224 224
225 225 @version = Version.find(7)
226 226 # Separate hierarchy
227 227 project_1_issue = Issue.find(1)
228 228 project_1_issue.fixed_version = @version
229 229 assert project_1_issue.save, project_1_issue.errors.full_messages.to_s
230 230
231 231 project_5_issue = Issue.find(6)
232 232 project_5_issue.fixed_version = @version
233 233 assert project_5_issue.save
234 234
235 235 # Project
236 236 project_2_issue = Issue.find(4)
237 237 project_2_issue.fixed_version = @version
238 238 assert project_2_issue.save
239 239
240 240 # Update the sharing
241 241 @version.sharing = 'none'
242 242 assert @version.save
243 243
244 244 # Project 1 now out of the shared scope
245 245 project_1_issue.reload
246 assert_equal nil, project_1_issue.fixed_version,
246 assert_nil project_1_issue.fixed_version,
247 247 "Fixed version is still set after changing the Version's sharing"
248 248
249 249 # Project 5 now out of the shared scope
250 250 project_5_issue.reload
251 assert_equal nil, project_5_issue.fixed_version,
251 assert_nil project_5_issue.fixed_version,
252 252 "Fixed version is still set after changing the Version's sharing"
253 253
254 254 # Project 2 issue remains
255 255 project_2_issue.reload
256 256 assert_equal @version, project_2_issue.fixed_version
257 257 end
258 258
259 259 def test_deletable_should_return_true_when_not_referenced
260 260 version = Version.generate!
261 261
262 262 assert_equal true, version.deletable?
263 263 end
264 264
265 265 def test_deletable_should_return_false_when_referenced_by_an_issue
266 266 version = Version.generate!
267 267 Issue.generate!(:fixed_version => version)
268 268
269 269 assert_equal false, version.deletable?
270 270 end
271 271
272 272 def test_deletable_should_return_false_when_referenced_by_a_custom_field
273 273 version = Version.generate!
274 274 field = IssueCustomField.generate!(:field_format => 'version')
275 275 value = CustomValue.create!(:custom_field => field, :customized => Issue.first, :value => version.id)
276 276
277 277 assert_equal false, version.deletable?
278 278 end
279 279
280 280 private
281 281
282 282 def add_issue(version, attributes={})
283 283 Issue.create!({:project => version.project,
284 284 :fixed_version => version,
285 285 :subject => 'Test',
286 286 :author => User.first,
287 287 :tracker => version.project.trackers.first}.merge(attributes))
288 288 end
289 289
290 290 def assert_progress_equal(expected_float, actual_float, message="")
291 291 assert_in_delta(expected_float, actual_float, 0.000001, message="")
292 292 end
293 293 end
@@ -1,201 +1,201
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class WikiPageTest < ActiveSupport::TestCase
21 21 fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
22 22
23 23 def setup
24 24 @wiki = Wiki.find(1)
25 25 @page = @wiki.pages.first
26 26 end
27 27
28 28 def test_create
29 29 page = WikiPage.new(:wiki => @wiki)
30 30 assert !page.save
31 31 assert_equal 1, page.errors.count
32 32
33 33 page.title = "Page"
34 34 assert page.save
35 35 page.reload
36 36 assert !page.protected?
37 37
38 38 @wiki.reload
39 39 assert @wiki.pages.include?(page)
40 40 end
41 41
42 42 def test_sidebar_should_be_protected_by_default
43 43 page = @wiki.find_or_new_page('sidebar')
44 44 assert page.new_record?
45 45 assert page.protected?
46 46 end
47 47
48 48 def test_find_or_new_page
49 49 page = @wiki.find_or_new_page("CookBook documentation")
50 50 assert_kind_of WikiPage, page
51 51 assert !page.new_record?
52 52
53 53 page = @wiki.find_or_new_page("Non existing page")
54 54 assert_kind_of WikiPage, page
55 55 assert page.new_record?
56 56 end
57 57
58 58 def test_parent_title
59 59 page = WikiPage.find_by_title('Another_page')
60 60 assert_nil page.parent_title
61 61
62 62 page = WikiPage.find_by_title('Page_with_an_inline_image')
63 63 assert_equal 'CookBook documentation', page.parent_title
64 64 end
65 65
66 66 def test_assign_parent
67 67 page = WikiPage.find_by_title('Another_page')
68 68 page.parent_title = 'CookBook documentation'
69 69 assert page.save
70 70 page.reload
71 71 assert_equal WikiPage.find_by_title('CookBook_documentation'), page.parent
72 72 end
73 73
74 74 def test_unassign_parent
75 75 page = WikiPage.find_by_title('Page_with_an_inline_image')
76 76 page.parent_title = ''
77 77 assert page.save
78 78 page.reload
79 79 assert_nil page.parent
80 80 end
81 81
82 82 def test_parent_validation
83 83 page = WikiPage.find_by_title('CookBook_documentation')
84 84
85 85 # A page that doesn't exist
86 86 page.parent_title = 'Unknown title'
87 87 assert !page.save
88 88 assert_include I18n.translate('activerecord.errors.messages.invalid'),
89 89 page.errors[:parent_title]
90 90 # A child page
91 91 page.parent_title = 'Page_with_an_inline_image'
92 92 assert !page.save
93 93 assert_include I18n.translate('activerecord.errors.messages.circular_dependency'),
94 94 page.errors[:parent_title]
95 95 # The page itself
96 96 page.parent_title = 'CookBook_documentation'
97 97 assert !page.save
98 98 assert_include I18n.translate('activerecord.errors.messages.circular_dependency'),
99 99 page.errors[:parent_title]
100 100 page.parent_title = 'Another_page'
101 101 assert page.save
102 102 end
103 103
104 104 def test_move_child_should_clear_parent
105 105 parent = WikiPage.create!(:wiki_id => 1, :title => 'Parent')
106 106 child = WikiPage.create!(:wiki_id => 1, :title => 'Child', :parent => parent)
107 107
108 108 child.wiki_id = 2
109 109 child.save!
110 assert_equal nil, child.reload.parent_id
110 assert_nil child.reload.parent_id
111 111 end
112 112
113 113 def test_move_parent_should_move_child_page
114 114 parent = WikiPage.create!(:wiki_id => 1, :title => 'Parent')
115 115 child = WikiPage.create!(:wiki_id => 1, :title => 'Child', :parent => parent)
116 116 parent.reload
117 117
118 118 parent.wiki_id = 2
119 119 parent.save!
120 120 assert_equal 2, child.reload.wiki_id
121 121 assert_equal parent, child.parent
122 122 end
123 123
124 124 def test_move_parent_with_child_with_duplicate_name_should_not_move_child
125 125 parent = WikiPage.create!(:wiki_id => 1, :title => 'Parent')
126 126 child = WikiPage.create!(:wiki_id => 1, :title => 'Child', :parent_id => parent.id)
127 127 parent.reload
128 128 # page with the same name as the child in the target wiki
129 129 WikiPage.create!(:wiki_id => 2, :title => 'Child')
130 130
131 131 parent.wiki_id = 2
132 132 parent.save!
133 133
134 134 parent.reload
135 135 assert_equal 2, parent.wiki_id
136 136
137 137 child.reload
138 138 assert_equal 1, child.wiki_id
139 139 assert_nil child.parent_id
140 140 end
141 141
142 142 def test_destroy
143 143 page = WikiPage.find(1)
144 144 page.destroy
145 145 assert_nil WikiPage.find_by_id(1)
146 146 # make sure that page content and its history are deleted
147 147 assert_equal 0, WikiContent.where(:page_id => 1).count
148 148 assert_equal 0, WikiContent.versioned_class.where(:page_id => 1).count
149 149 end
150 150
151 151 def test_destroy_should_not_nullify_children
152 152 page = WikiPage.find(2)
153 153 child_ids = page.child_ids
154 154 assert child_ids.any?
155 155 page.destroy
156 156 assert_nil WikiPage.find_by_id(2)
157 157
158 158 children = WikiPage.where(:id => child_ids)
159 159 assert_equal child_ids.size, children.count
160 160 children.each do |child|
161 161 assert_nil child.parent_id
162 162 end
163 163 end
164 164
165 165 def test_with_updated_on_scope_should_preload_updated_on_and_version
166 166 page = WikiPage.with_updated_on.where(:id => 1).first
167 167 # make the assertions fail if attributes are not preloaded
168 168 WikiContent.update_all(:updated_on => '2001-01-01 10:00:00', :version => 1)
169 169
170 170 assert_equal Time.gm(2007, 3, 6, 23, 10, 51), page.updated_on
171 171 assert_equal 3, page.version
172 172 end
173 173
174 174 def test_descendants
175 175 page = WikiPage.create!(:wiki => @wiki, :title => 'Parent')
176 176 child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page)
177 177 child11 = WikiPage.create!(:wiki => @wiki, :title => 'Child11', :parent => child1)
178 178 child111 = WikiPage.create!(:wiki => @wiki, :title => 'Child111', :parent => child11)
179 179 child2 = WikiPage.create!(:wiki => @wiki, :title => 'Child2', :parent => page)
180 180
181 181 assert_equal %w(Child1 Child11 Child111 Child2), page.descendants.map(&:title).sort
182 182 assert_equal %w(Child1 Child11 Child111 Child2), page.descendants(nil).map(&:title).sort
183 183 assert_equal %w(Child1 Child11 Child2), page.descendants(2).map(&:title).sort
184 184 assert_equal %w(Child1 Child2), page.descendants(1).map(&:title).sort
185 185
186 186 assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants.map(&:title).sort
187 187 assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants(nil).map(&:title).sort
188 188 assert_equal %w(Child1 Child11 Child2 Parent), page.self_and_descendants(2).map(&:title).sort
189 189 assert_equal %w(Child1 Child2 Parent), page.self_and_descendants(1).map(&:title).sort
190 190 end
191 191
192 192 def test_diff_for_page_with_deleted_version_should_pick_the_previous_available_version
193 193 WikiContent::Version.find_by_page_id_and_version(1, 2).destroy
194 194
195 195 page = WikiPage.find(1)
196 196 diff = page.diff(3)
197 197 assert_not_nil diff
198 198 assert_equal 3, diff.content_to.version
199 199 assert_equal 1, diff.content_from.version
200 200 end
201 201 end
General Comments 0
You need to be logged in to leave comments. Login now