##// END OF EJS Templates
Merged r14795 and r14796 (#21150)....
Jean-Philippe Lang -
r14456:5bd5738750b0
parent child
Show More
@@ -1,131 +1,131
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 class TimeEntry < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20 # could have used polymorphic association
21 21 # project association here allows easy loading of time entries at project level with one database trip
22 22 belongs_to :project
23 23 belongs_to :issue
24 24 belongs_to :user
25 25 belongs_to :activity, :class_name => 'TimeEntryActivity'
26 26
27 27 attr_protected :user_id, :tyear, :tmonth, :tweek
28 28
29 29 acts_as_customizable
30 30 acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
31 31 :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
32 32 :author => :user,
33 33 :group => :issue,
34 34 :description => :comments
35 35
36 36 acts_as_activity_provider :timestamp => "#{table_name}.created_on",
37 37 :author_key => :user_id,
38 38 :scope => joins(:project).preload(:project)
39 39
40 40 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
41 41 validates_numericality_of :hours, :allow_nil => true, :message => :invalid
42 42 validates_length_of :comments, :maximum => 255, :allow_nil => true
43 43 validates :spent_on, :date => true
44 44 before_validation :set_project_if_nil
45 45 validate :validate_time_entry
46 46
47 47 scope :visible, lambda {|*args|
48 48 joins(:project).
49 49 where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args))
50 50 }
51 51 scope :on_issue, lambda {|issue|
52 52 joins(:issue).
53 53 where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}")
54 54 }
55 55
56 56 safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
57 57
58 58 def initialize(attributes=nil, *args)
59 59 super
60 60 if new_record? && self.activity.nil?
61 61 if default_activity = TimeEntryActivity.default
62 62 self.activity_id = default_activity.id
63 63 end
64 64 self.hours = nil if hours == 0
65 65 end
66 66 end
67 67
68 68 def safe_attributes=(attrs, user=User.current)
69 69 if attrs
70 70 attrs = super(attrs)
71 71 if issue_id_changed? && issue
72 if user.allowed_to?(:log_time, issue.project)
72 if issue.visible?(user) && user.allowed_to?(:log_time, issue.project)
73 73 if attrs[:project_id].blank? && issue.project_id != project_id
74 74 self.project_id = issue.project_id
75 75 end
76 76 @invalid_issue_id = nil
77 77 else
78 78 @invalid_issue_id = issue_id
79 79 end
80 80 end
81 81 end
82 82 attrs
83 83 end
84 84
85 85 def set_project_if_nil
86 86 self.project = issue.project if issue && project.nil?
87 87 end
88 88
89 89 def validate_time_entry
90 90 errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
91 91 errors.add :project_id, :invalid if project.nil?
92 92 errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project) || @invalid_issue_id
93 93 end
94 94
95 95 def hours=(h)
96 96 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
97 97 end
98 98
99 99 def hours
100 100 h = read_attribute(:hours)
101 101 if h.is_a?(Float)
102 102 h.round(2)
103 103 else
104 104 h
105 105 end
106 106 end
107 107
108 108 # tyear, tmonth, tweek assigned where setting spent_on attributes
109 109 # these attributes make time aggregations easier
110 110 def spent_on=(date)
111 111 super
112 112 self.tyear = spent_on ? spent_on.year : nil
113 113 self.tmonth = spent_on ? spent_on.month : nil
114 114 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
115 115 end
116 116
117 117 # Returns true if the time entry can be edited by usr, otherwise false
118 118 def editable_by?(usr)
119 119 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
120 120 end
121 121
122 122 # Returns the custom_field_values that can be edited by the given user
123 123 def editable_custom_field_values(user=nil)
124 124 visible_custom_field_values
125 125 end
126 126
127 127 # Returns the custom fields that can be edited by the given user
128 128 def editable_custom_fields(user=nil)
129 129 editable_custom_field_values(user).map(&:custom_field).uniq
130 130 end
131 131 end
@@ -1,34 +1,36
1 1 <%= error_messages_for 'time_entry' %>
2 2 <%= back_url_hidden_field_tag %>
3 3
4 4 <div class="box tabular">
5 5 <% if @time_entry.new_record? %>
6 6 <% if params[:project_id] %>
7 7 <%= hidden_field_tag 'project_id', params[:project_id] %>
8 8 <% elsif params[:issue_id] %>
9 9 <%= hidden_field_tag 'issue_id', params[:issue_id] %>
10 10 <% else %>
11 11 <p><%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).to_a, :selected => @time_entry.project, :include_blank => true) %></p>
12 12 <% end %>
13 13 <% end %>
14 14 <p>
15 15 <%= f.text_field :issue_id, :size => 6 %>
16 <span id="time_entry_issue"><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></span>
16 <% if @time_entry.issue.try(:visible?) %>
17 <span id="time_entry_issue"><%= "#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}" %></span>
18 <% end %>
17 19 </p>
18 20 <p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
19 21 <p><%= f.text_field :hours, :size => 6, :required => true %></p>
20 22 <p><%= f.text_field :comments, :size => 100, :maxlength => 255 %></p>
21 23 <p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
22 24 <% @time_entry.custom_field_values.each do |value| %>
23 25 <p><%= custom_field_tag_with_label :time_entry, value %></p>
24 26 <% end %>
25 27 <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
26 28 </div>
27 29
28 30 <%= javascript_tag do %>
29 31 observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', {
30 32 select: function(event, ui) {
31 33 $('#time_entry_issue').text(ui.item.label);
32 34 }
33 35 });
34 36 <% end %>
@@ -1,802 +1,819
1 1 # -*- coding: utf-8 -*-
2 2 # Redmine - project management software
3 3 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require File.expand_path('../../test_helper', __FILE__)
20 20
21 21 class TimelogControllerTest < ActionController::TestCase
22 22 fixtures :projects, :enabled_modules, :roles, :members,
23 23 :member_roles, :issues, :time_entries, :users,
24 24 :trackers, :enumerations, :issue_statuses,
25 25 :custom_fields, :custom_values,
26 26 :projects_trackers, :custom_fields_trackers,
27 27 :custom_fields_projects
28 28
29 29 include Redmine::I18n
30 30
31 31 def test_new
32 32 @request.session[:user_id] = 3
33 33 get :new
34 34 assert_response :success
35 35 assert_template 'new'
36 36 assert_select 'input[name=?][type=hidden]', 'project_id', 0
37 37 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
38 38 assert_select 'select[name=?]', 'time_entry[project_id]' do
39 39 # blank option for project
40 40 assert_select 'option[value=""]'
41 41 end
42 42 end
43 43
44 44 def test_new_with_project_id
45 45 @request.session[:user_id] = 3
46 46 get :new, :project_id => 1
47 47 assert_response :success
48 48 assert_template 'new'
49 49 assert_select 'input[name=?][type=hidden]', 'project_id'
50 50 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
51 51 assert_select 'select[name=?]', 'time_entry[project_id]', 0
52 52 end
53 53
54 54 def test_new_with_issue_id
55 55 @request.session[:user_id] = 3
56 56 get :new, :issue_id => 2
57 57 assert_response :success
58 58 assert_template 'new'
59 59 assert_select 'input[name=?][type=hidden]', 'project_id', 0
60 60 assert_select 'input[name=?][type=hidden]', 'issue_id'
61 61 assert_select 'select[name=?]', 'time_entry[project_id]', 0
62 62 end
63 63
64 64 def test_new_without_project_should_prefill_the_form
65 65 @request.session[:user_id] = 3
66 66 get :new, :time_entry => {:project_id => '1'}
67 67 assert_response :success
68 68 assert_template 'new'
69 69 assert_select 'select[name=?]', 'time_entry[project_id]' do
70 70 assert_select 'option[value="1"][selected=selected]'
71 71 end
72 72 end
73 73
74 74 def test_new_without_project_should_deny_without_permission
75 75 Role.all.each {|role| role.remove_permission! :log_time}
76 76 @request.session[:user_id] = 3
77 77
78 78 get :new
79 79 assert_response 403
80 80 end
81 81
82 82 def test_new_should_select_default_activity
83 83 @request.session[:user_id] = 3
84 84 get :new, :project_id => 1
85 85 assert_response :success
86 86 assert_select 'select[name=?]', 'time_entry[activity_id]' do
87 87 assert_select 'option[selected=selected]', :text => 'Development'
88 88 end
89 89 end
90 90
91 91 def test_new_should_only_show_active_time_entry_activities
92 92 @request.session[:user_id] = 3
93 93 get :new, :project_id => 1
94 94 assert_response :success
95 95 assert_select 'option', :text => 'Inactive Activity', :count => 0
96 96 end
97 97
98 98 def test_get_edit_existing_time
99 99 @request.session[:user_id] = 2
100 100 get :edit, :id => 2, :project_id => nil
101 101 assert_response :success
102 102 assert_template 'edit'
103 103 assert_select 'form[action=?]', '/time_entries/2'
104 104 end
105 105
106 106 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
107 107 te = TimeEntry.find(1)
108 108 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
109 109 te.save!
110 110
111 111 @request.session[:user_id] = 1
112 112 get :edit, :project_id => 1, :id => 1
113 113 assert_response :success
114 114 assert_template 'edit'
115 115 # Blank option since nothing is pre-selected
116 116 assert_select 'option', :text => '--- Please select ---'
117 117 end
118 118
119 119 def test_post_create
120 120 @request.session[:user_id] = 3
121 121 assert_difference 'TimeEntry.count' do
122 122 post :create, :project_id => 1,
123 123 :time_entry => {:comments => 'Some work on TimelogControllerTest',
124 124 # Not the default activity
125 125 :activity_id => '11',
126 126 :spent_on => '2008-03-14',
127 127 :issue_id => '1',
128 128 :hours => '7.3'}
129 129 assert_redirected_to '/projects/ecookbook/time_entries'
130 130 end
131 131
132 132 t = TimeEntry.order('id DESC').first
133 133 assert_not_nil t
134 134 assert_equal 'Some work on TimelogControllerTest', t.comments
135 135 assert_equal 1, t.project_id
136 136 assert_equal 1, t.issue_id
137 137 assert_equal 11, t.activity_id
138 138 assert_equal 7.3, t.hours
139 139 assert_equal 3, t.user_id
140 140 end
141 141
142 142 def test_post_create_with_blank_issue
143 143 @request.session[:user_id] = 3
144 144 assert_difference 'TimeEntry.count' do
145 145 post :create, :project_id => 1,
146 146 :time_entry => {:comments => 'Some work on TimelogControllerTest',
147 147 # Not the default activity
148 148 :activity_id => '11',
149 149 :issue_id => '',
150 150 :spent_on => '2008-03-14',
151 151 :hours => '7.3'}
152 152 assert_redirected_to '/projects/ecookbook/time_entries'
153 153 end
154 154
155 155 t = TimeEntry.order('id DESC').first
156 156 assert_not_nil t
157 157 assert_equal 'Some work on TimelogControllerTest', t.comments
158 158 assert_equal 1, t.project_id
159 159 assert_nil t.issue_id
160 160 assert_equal 11, t.activity_id
161 161 assert_equal 7.3, t.hours
162 162 assert_equal 3, t.user_id
163 163 end
164 164
165 165 def test_create_on_project_with_time_tracking_disabled_should_fail
166 166 Project.find(1).disable_module! :time_tracking
167 167
168 168 @request.session[:user_id] = 2
169 169 assert_no_difference 'TimeEntry.count' do
170 170 post :create, :time_entry => {
171 171 :project_id => '1', :issue_id => '',
172 172 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
173 173 }
174 174 end
175 175 end
176 176
177 177 def test_create_on_project_without_permission_should_fail
178 178 Role.find(1).remove_permission! :log_time
179 179
180 180 @request.session[:user_id] = 2
181 181 assert_no_difference 'TimeEntry.count' do
182 182 post :create, :time_entry => {
183 183 :project_id => '1', :issue_id => '',
184 184 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
185 185 }
186 186 end
187 187 end
188 188
189 189 def test_create_on_issue_in_project_with_time_tracking_disabled_should_fail
190 190 Project.find(1).disable_module! :time_tracking
191 191
192 192 @request.session[:user_id] = 2
193 193 assert_no_difference 'TimeEntry.count' do
194 194 post :create, :time_entry => {
195 195 :project_id => '', :issue_id => '1',
196 196 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
197 197 }
198 198 assert_select_error /Issue is invalid/
199 199 end
200 200 end
201 201
202 202 def test_create_on_issue_in_project_without_permission_should_fail
203 203 Role.find(1).remove_permission! :log_time
204 204
205 205 @request.session[:user_id] = 2
206 206 assert_no_difference 'TimeEntry.count' do
207 207 post :create, :time_entry => {
208 208 :project_id => '', :issue_id => '1',
209 209 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
210 210 }
211 211 assert_select_error /Issue is invalid/
212 212 end
213 213 end
214 214
215 def test_create_on_issue_that_is_not_visible_should_not_disclose_subject
216 issue = Issue.generate!(:subject => "issue_that_is_not_visible", :is_private => true)
217 assert !issue.visible?(User.find(3))
218
219 @request.session[:user_id] = 3
220 assert_no_difference 'TimeEntry.count' do
221 post :create, :time_entry => {
222 :project_id => '', :issue_id => issue.id.to_s,
223 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
224 }
225 end
226 assert_select_error /Issue is invalid/
227 assert_select "input[name=?][value=?]", "time_entry[issue_id]", issue.id.to_s
228 assert_select "#time_entry_issue", 0
229 assert !response.body.include?('issue_that_is_not_visible')
230 end
231
215 232 def test_create_and_continue_at_project_level
216 233 @request.session[:user_id] = 2
217 234 assert_difference 'TimeEntry.count' do
218 235 post :create, :time_entry => {:project_id => '1',
219 236 :activity_id => '11',
220 237 :issue_id => '',
221 238 :spent_on => '2008-03-14',
222 239 :hours => '7.3'},
223 240 :continue => '1'
224 241 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
225 242 end
226 243 end
227 244
228 245 def test_create_and_continue_at_issue_level
229 246 @request.session[:user_id] = 2
230 247 assert_difference 'TimeEntry.count' do
231 248 post :create, :time_entry => {:project_id => '',
232 249 :activity_id => '11',
233 250 :issue_id => '1',
234 251 :spent_on => '2008-03-14',
235 252 :hours => '7.3'},
236 253 :continue => '1'
237 254 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
238 255 end
239 256 end
240 257
241 258 def test_create_and_continue_with_project_id
242 259 @request.session[:user_id] = 2
243 260 assert_difference 'TimeEntry.count' do
244 261 post :create, :project_id => 1,
245 262 :time_entry => {:activity_id => '11',
246 263 :issue_id => '',
247 264 :spent_on => '2008-03-14',
248 265 :hours => '7.3'},
249 266 :continue => '1'
250 267 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D='
251 268 end
252 269 end
253 270
254 271 def test_create_and_continue_with_issue_id
255 272 @request.session[:user_id] = 2
256 273 assert_difference 'TimeEntry.count' do
257 274 post :create, :issue_id => 1,
258 275 :time_entry => {:activity_id => '11',
259 276 :issue_id => '1',
260 277 :spent_on => '2008-03-14',
261 278 :hours => '7.3'},
262 279 :continue => '1'
263 280 assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
264 281 end
265 282 end
266 283
267 284 def test_create_without_log_time_permission_should_be_denied
268 285 @request.session[:user_id] = 2
269 286 Role.find_by_name('Manager').remove_permission! :log_time
270 287 post :create, :project_id => 1,
271 288 :time_entry => {:activity_id => '11',
272 289 :issue_id => '',
273 290 :spent_on => '2008-03-14',
274 291 :hours => '7.3'}
275 292
276 293 assert_response 403
277 294 end
278 295
279 296 def test_create_without_project_and_issue_should_fail
280 297 @request.session[:user_id] = 2
281 298 post :create, :time_entry => {:issue_id => ''}
282 299
283 300 assert_response :success
284 301 assert_template 'new'
285 302 end
286 303
287 304 def test_create_with_failure
288 305 @request.session[:user_id] = 2
289 306 post :create, :project_id => 1,
290 307 :time_entry => {:activity_id => '',
291 308 :issue_id => '',
292 309 :spent_on => '2008-03-14',
293 310 :hours => '7.3'}
294 311
295 312 assert_response :success
296 313 assert_template 'new'
297 314 end
298 315
299 316 def test_create_without_project
300 317 @request.session[:user_id] = 2
301 318 assert_difference 'TimeEntry.count' do
302 319 post :create, :time_entry => {:project_id => '1',
303 320 :activity_id => '11',
304 321 :issue_id => '',
305 322 :spent_on => '2008-03-14',
306 323 :hours => '7.3'}
307 324 end
308 325
309 326 assert_redirected_to '/projects/ecookbook/time_entries'
310 327 time_entry = TimeEntry.order('id DESC').first
311 328 assert_equal 1, time_entry.project_id
312 329 end
313 330
314 331 def test_create_without_project_should_fail_with_issue_not_inside_project
315 332 @request.session[:user_id] = 2
316 333 assert_no_difference 'TimeEntry.count' do
317 334 post :create, :time_entry => {:project_id => '1',
318 335 :activity_id => '11',
319 336 :issue_id => '5',
320 337 :spent_on => '2008-03-14',
321 338 :hours => '7.3'}
322 339 end
323 340
324 341 assert_response :success
325 342 assert assigns(:time_entry).errors[:issue_id].present?
326 343 end
327 344
328 345 def test_create_without_project_should_deny_without_permission
329 346 @request.session[:user_id] = 2
330 347 Project.find(3).disable_module!(:time_tracking)
331 348
332 349 assert_no_difference 'TimeEntry.count' do
333 350 post :create, :time_entry => {:project_id => '3',
334 351 :activity_id => '11',
335 352 :issue_id => '',
336 353 :spent_on => '2008-03-14',
337 354 :hours => '7.3'}
338 355 end
339 356
340 357 assert_response 403
341 358 end
342 359
343 360 def test_create_without_project_with_failure
344 361 @request.session[:user_id] = 2
345 362 assert_no_difference 'TimeEntry.count' do
346 363 post :create, :time_entry => {:project_id => '1',
347 364 :activity_id => '11',
348 365 :issue_id => '',
349 366 :spent_on => '2008-03-14',
350 367 :hours => ''}
351 368 end
352 369
353 370 assert_response :success
354 371 assert_select 'select[name=?]', 'time_entry[project_id]' do
355 372 assert_select 'option[value="1"][selected=selected]'
356 373 end
357 374 end
358 375
359 376 def test_update
360 377 entry = TimeEntry.find(1)
361 378 assert_equal 1, entry.issue_id
362 379 assert_equal 2, entry.user_id
363 380
364 381 @request.session[:user_id] = 1
365 382 put :update, :id => 1,
366 383 :time_entry => {:issue_id => '2',
367 384 :hours => '8'}
368 385 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
369 386 entry.reload
370 387
371 388 assert_equal 8, entry.hours
372 389 assert_equal 2, entry.issue_id
373 390 assert_equal 2, entry.user_id
374 391 end
375 392
376 393 def test_update_should_allow_to_change_issue_to_another_project
377 394 entry = TimeEntry.generate!(:issue_id => 1)
378 395
379 396 @request.session[:user_id] = 1
380 397 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
381 398 assert_response 302
382 399 entry.reload
383 400
384 401 assert_equal 5, entry.issue_id
385 402 assert_equal 3, entry.project_id
386 403 end
387 404
388 405 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
389 406 entry = TimeEntry.generate!(:issue_id => 1)
390 407 Project.find(3).disable_module!(:time_tracking)
391 408
392 409 @request.session[:user_id] = 1
393 410 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
394 411 assert_response 200
395 412 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
396 413 end
397 414
398 415 def test_get_bulk_edit
399 416 @request.session[:user_id] = 2
400 417 get :bulk_edit, :ids => [1, 2]
401 418 assert_response :success
402 419 assert_template 'bulk_edit'
403 420
404 421 assert_select 'ul#bulk-selection' do
405 422 assert_select 'li', 2
406 423 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
407 424 end
408 425
409 426 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
410 427 # System wide custom field
411 428 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
412 429
413 430 # Activities
414 431 assert_select 'select[name=?]', 'time_entry[activity_id]' do
415 432 assert_select 'option[value=""]', :text => '(No change)'
416 433 assert_select 'option[value="9"]', :text => 'Design'
417 434 end
418 435 end
419 436 end
420 437
421 438 def test_get_bulk_edit_on_different_projects
422 439 @request.session[:user_id] = 2
423 440 get :bulk_edit, :ids => [1, 2, 6]
424 441 assert_response :success
425 442 assert_template 'bulk_edit'
426 443 end
427 444
428 445 def test_bulk_edit_with_edit_own_time_entries_permission
429 446 @request.session[:user_id] = 2
430 447 Role.find_by_name('Manager').remove_permission! :edit_time_entries
431 448 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
432 449 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
433 450
434 451 get :bulk_edit, :ids => ids
435 452 assert_response :success
436 453 end
437 454
438 455 def test_bulk_update
439 456 @request.session[:user_id] = 2
440 457 # update time entry activity
441 458 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
442 459
443 460 assert_response 302
444 461 # check that the issues were updated
445 462 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
446 463 end
447 464
448 465 def test_bulk_update_with_failure
449 466 @request.session[:user_id] = 2
450 467 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
451 468
452 469 assert_response 302
453 470 assert_match /Failed to save 2 time entrie/, flash[:error]
454 471 end
455 472
456 473 def test_bulk_update_on_different_projects
457 474 @request.session[:user_id] = 2
458 475 # makes user a manager on the other project
459 476 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
460 477
461 478 # update time entry activity
462 479 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
463 480
464 481 assert_response 302
465 482 # check that the issues were updated
466 483 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
467 484 end
468 485
469 486 def test_bulk_update_on_different_projects_without_rights
470 487 @request.session[:user_id] = 3
471 488 user = User.find(3)
472 489 action = { :controller => "timelog", :action => "bulk_update" }
473 490 assert user.allowed_to?(action, TimeEntry.find(1).project)
474 491 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
475 492 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
476 493 assert_response 403
477 494 end
478 495
479 496 def test_bulk_update_with_edit_own_time_entries_permission
480 497 @request.session[:user_id] = 2
481 498 Role.find_by_name('Manager').remove_permission! :edit_time_entries
482 499 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
483 500 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
484 501
485 502 post :bulk_update, :ids => ids, :time_entry => { :activity_id => 9 }
486 503 assert_response 302
487 504 end
488 505
489 506 def test_bulk_update_with_edit_own_time_entries_permissions_should_be_denied_for_time_entries_of_other_user
490 507 @request.session[:user_id] = 2
491 508 Role.find_by_name('Manager').remove_permission! :edit_time_entries
492 509 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
493 510
494 511 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9 }
495 512 assert_response 403
496 513 end
497 514
498 515 def test_bulk_update_custom_field
499 516 @request.session[:user_id] = 2
500 517 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
501 518
502 519 assert_response 302
503 520 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
504 521 end
505 522
506 523 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
507 524 @request.session[:user_id] = 2
508 525 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
509 526
510 527 assert_response :redirect
511 528 assert_redirected_to '/time_entries'
512 529 end
513 530
514 531 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
515 532 @request.session[:user_id] = 2
516 533 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
517 534
518 535 assert_response :redirect
519 536 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
520 537 end
521 538
522 539 def test_post_bulk_update_without_edit_permission_should_be_denied
523 540 @request.session[:user_id] = 2
524 541 Role.find_by_name('Manager').remove_permission! :edit_time_entries
525 542 post :bulk_update, :ids => [1,2]
526 543
527 544 assert_response 403
528 545 end
529 546
530 547 def test_destroy
531 548 @request.session[:user_id] = 2
532 549 delete :destroy, :id => 1
533 550 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
534 551 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
535 552 assert_nil TimeEntry.find_by_id(1)
536 553 end
537 554
538 555 def test_destroy_should_fail
539 556 # simulate that this fails (e.g. due to a plugin), see #5700
540 557 TimeEntry.any_instance.expects(:destroy).returns(false)
541 558
542 559 @request.session[:user_id] = 2
543 560 delete :destroy, :id => 1
544 561 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
545 562 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
546 563 assert_not_nil TimeEntry.find_by_id(1)
547 564 end
548 565
549 566 def test_index_all_projects
550 567 get :index
551 568 assert_response :success
552 569 assert_template 'index'
553 570 assert_not_nil assigns(:total_hours)
554 571 assert_equal "162.90", "%.2f" % assigns(:total_hours)
555 572 assert_select 'form#query_form[action=?]', '/time_entries'
556 573 end
557 574
558 575 def test_index_all_projects_should_show_log_time_link
559 576 @request.session[:user_id] = 2
560 577 get :index
561 578 assert_response :success
562 579 assert_template 'index'
563 580 assert_select 'a[href=?]', '/time_entries/new', :text => /Log time/
564 581 end
565 582
566 583 def test_index_my_spent_time
567 584 @request.session[:user_id] = 2
568 585 get :index, :user_id => 'me'
569 586 assert_response :success
570 587 assert_template 'index'
571 588 assert assigns(:entries).all? {|entry| entry.user_id == 2}
572 589 end
573 590
574 591 def test_index_at_project_level
575 592 get :index, :project_id => 'ecookbook'
576 593 assert_response :success
577 594 assert_template 'index'
578 595 assert_not_nil assigns(:entries)
579 596 assert_equal 4, assigns(:entries).size
580 597 # project and subproject
581 598 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
582 599 assert_not_nil assigns(:total_hours)
583 600 assert_equal "162.90", "%.2f" % assigns(:total_hours)
584 601 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
585 602 end
586 603
587 604 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
588 605 entry = TimeEntry.generate!(:project => Project.find(3))
589 606
590 607 with_settings :display_subprojects_issues => '0' do
591 608 get :index, :project_id => 'ecookbook'
592 609 assert_response :success
593 610 assert_template 'index'
594 611 assert_not_include entry, assigns(:entries)
595 612 end
596 613 end
597 614
598 615 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
599 616 entry = TimeEntry.generate!(:project => Project.find(3))
600 617
601 618 with_settings :display_subprojects_issues => '0' do
602 619 get :index, :project_id => 'ecookbook', :subproject_id => 3
603 620 assert_response :success
604 621 assert_template 'index'
605 622 assert_include entry, assigns(:entries)
606 623 end
607 624 end
608 625
609 626 def test_index_at_project_level_with_date_range
610 627 get :index, :project_id => 'ecookbook',
611 628 :f => ['spent_on'],
612 629 :op => {'spent_on' => '><'},
613 630 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
614 631 assert_response :success
615 632 assert_template 'index'
616 633 assert_not_nil assigns(:entries)
617 634 assert_equal 3, assigns(:entries).size
618 635 assert_not_nil assigns(:total_hours)
619 636 assert_equal "12.90", "%.2f" % assigns(:total_hours)
620 637 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
621 638 end
622 639
623 640 def test_index_at_project_level_with_date_range_using_from_and_to_params
624 641 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
625 642 assert_response :success
626 643 assert_template 'index'
627 644 assert_not_nil assigns(:entries)
628 645 assert_equal 3, assigns(:entries).size
629 646 assert_not_nil assigns(:total_hours)
630 647 assert_equal "12.90", "%.2f" % assigns(:total_hours)
631 648 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
632 649 end
633 650
634 651 def test_index_at_project_level_with_period
635 652 get :index, :project_id => 'ecookbook',
636 653 :f => ['spent_on'],
637 654 :op => {'spent_on' => '>t-'},
638 655 :v => {'spent_on' => ['7']}
639 656 assert_response :success
640 657 assert_template 'index'
641 658 assert_not_nil assigns(:entries)
642 659 assert_not_nil assigns(:total_hours)
643 660 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
644 661 end
645 662
646 663 def test_index_at_issue_level
647 664 get :index, :issue_id => 1
648 665 assert_response :success
649 666 assert_template 'index'
650 667 assert_not_nil assigns(:entries)
651 668 assert_equal 2, assigns(:entries).size
652 669 assert_not_nil assigns(:total_hours)
653 670 assert_equal 154.25, assigns(:total_hours)
654 671 # display all time
655 672 assert_nil assigns(:from)
656 673 assert_nil assigns(:to)
657 674 assert_select 'form#query_form[action=?]', '/issues/1/time_entries'
658 675 end
659 676
660 677 def test_index_should_sort_by_spent_on_and_created_on
661 678 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
662 679 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
663 680 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
664 681
665 682 get :index, :project_id => 1,
666 683 :f => ['spent_on'],
667 684 :op => {'spent_on' => '><'},
668 685 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
669 686 assert_response :success
670 687 assert_equal [t2, t1, t3], assigns(:entries)
671 688
672 689 get :index, :project_id => 1,
673 690 :f => ['spent_on'],
674 691 :op => {'spent_on' => '><'},
675 692 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
676 693 :sort => 'spent_on'
677 694 assert_response :success
678 695 assert_equal [t3, t1, t2], assigns(:entries)
679 696 end
680 697
681 698 def test_index_with_filter_on_issue_custom_field
682 699 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
683 700 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
684 701
685 702 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
686 703 assert_response :success
687 704 assert_equal [entry], assigns(:entries)
688 705 end
689 706
690 707 def test_index_with_issue_custom_field_column
691 708 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
692 709 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
693 710
694 711 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
695 712 assert_response :success
696 713 assert_include :'issue.cf_2', assigns(:query).column_names
697 714 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
698 715 end
699 716
700 717 def test_index_with_time_entry_custom_field_column
701 718 field = TimeEntryCustomField.generate!(:field_format => 'string')
702 719 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
703 720 field_name = "cf_#{field.id}"
704 721
705 722 get :index, :c => ["hours", field_name]
706 723 assert_response :success
707 724 assert_include field_name.to_sym, assigns(:query).column_names
708 725 assert_select "td.#{field_name}", :text => 'CF Value'
709 726 end
710 727
711 728 def test_index_with_time_entry_custom_field_sorting
712 729 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
713 730 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
714 731 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
715 732 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
716 733 field_name = "cf_#{field.id}"
717 734
718 735 get :index, :c => ["hours", field_name], :sort => field_name
719 736 assert_response :success
720 737 assert_include field_name.to_sym, assigns(:query).column_names
721 738 assert_select "th a.sort", :text => 'String Field'
722 739
723 740 # Make sure that values are properly sorted
724 741 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
725 742 assert_equal 3, values.size
726 743 assert_equal values.sort, values
727 744 end
728 745
729 746 def test_index_atom_feed
730 747 get :index, :project_id => 1, :format => 'atom'
731 748 assert_response :success
732 749 assert_equal 'application/atom+xml', @response.content_type
733 750 assert_not_nil assigns(:items)
734 751 assert assigns(:items).first.is_a?(TimeEntry)
735 752 end
736 753
737 754 def test_index_at_project_level_should_include_csv_export_dialog
738 755 get :index, :project_id => 'ecookbook',
739 756 :f => ['spent_on'],
740 757 :op => {'spent_on' => '>='},
741 758 :v => {'spent_on' => ['2007-04-01']},
742 759 :c => ['spent_on', 'user']
743 760 assert_response :success
744 761
745 762 assert_select '#csv-export-options' do
746 763 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
747 764 # filter
748 765 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
749 766 assert_select 'input[name=?][value=?]', 'op[spent_on]', '>='
750 767 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
751 768 # columns
752 769 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
753 770 assert_select 'input[name=?][value=?]', 'c[]', 'user'
754 771 assert_select 'input[name=?]', 'c[]', 2
755 772 end
756 773 end
757 774 end
758 775
759 776 def test_index_cross_project_should_include_csv_export_dialog
760 777 get :index
761 778 assert_response :success
762 779
763 780 assert_select '#csv-export-options' do
764 781 assert_select 'form[action=?][method=get]', '/time_entries.csv'
765 782 end
766 783 end
767 784
768 785 def test_index_at_issue_level_should_include_csv_export_dialog
769 786 get :index, :issue_id => 3
770 787 assert_response :success
771 788
772 789 assert_select '#csv-export-options' do
773 790 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
774 791 end
775 792 end
776 793
777 794 def test_index_csv_all_projects
778 795 with_settings :date_format => '%m/%d/%Y' do
779 796 get :index, :format => 'csv'
780 797 assert_response :success
781 798 assert_equal 'text/csv; header=present', response.content_type
782 799 end
783 800 end
784 801
785 802 def test_index_csv
786 803 with_settings :date_format => '%m/%d/%Y' do
787 804 get :index, :project_id => 1, :format => 'csv'
788 805 assert_response :success
789 806 assert_equal 'text/csv; header=present', response.content_type
790 807 end
791 808 end
792 809
793 810 def test_index_csv_should_fill_issue_column_with_tracker_id_and_subject
794 811 issue = Issue.find(1)
795 812 entry = TimeEntry.generate!(:issue => issue, :comments => "Issue column content test")
796 813
797 814 get :index, :format => 'csv'
798 815 line = response.body.split("\n").detect {|l| l.include?(entry.comments)}
799 816 assert_not_nil line
800 817 assert_include "#{issue.tracker} #1: #{issue.subject}", line
801 818 end
802 819 end
General Comments 0
You need to be logged in to leave comments. Login now