##// END OF EJS Templates
Merged r14242 (#18580)....
Jean-Philippe Lang -
r13864:20f56ce0aed9
parent child
Show More
@@ -1,97 +1,97
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 ContextMenusController < ApplicationController
19 19 helper :watchers
20 20 helper :issues
21 21
22 22 before_filter :find_issues, :only => :issues
23 23
24 24 def issues
25 25 if (@issues.size == 1)
26 26 @issue = @issues.first
27 27 end
28 28 @issue_ids = @issues.map(&:id).sort
29 29
30 30 @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
31 31
32 32 @can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
33 33 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
34 34 :copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
35 35 :delete => User.current.allowed_to?(:delete_issues, @projects)
36 36 }
37 37 if @project
38 38 if @issue
39 39 @assignables = @issue.assignable_users
40 40 else
41 41 @assignables = @project.assignable_users
42 42 end
43 43 @trackers = @project.trackers
44 44 else
45 45 #when multiple projects, we only keep the intersection of each set
46 46 @assignables = @projects.map(&:assignable_users).reduce(:&)
47 47 @trackers = @projects.map(&:trackers).reduce(:&)
48 48 end
49 49 @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
50 50
51 51 @priorities = IssuePriority.active.reverse
52 52 @back = back_url
53 53
54 54 @options_by_custom_field = {}
55 55 if @can[:edit]
56 56 custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
57 57 custom_fields.each do |field|
58 58 values = field.possible_values_options(@projects)
59 59 if values.present?
60 60 @options_by_custom_field[field] = values
61 61 end
62 62 end
63 63 end
64 64
65 65 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
66 66 render :layout => false
67 67 end
68 68
69 69 def time_entries
70 70 @time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a
71 71 (render_404; return) unless @time_entries.present?
72 72 if (@time_entries.size == 1)
73 73 @time_entry = @time_entries.first
74 74 end
75 75
76 76 @projects = @time_entries.collect(&:project).compact.uniq
77 77 @project = @projects.first if @projects.size == 1
78 78 @activities = TimeEntryActivity.shared.active
79 @can = {:edit => User.current.allowed_to?(:edit_time_entries, @projects),
80 :delete => User.current.allowed_to?(:edit_time_entries, @projects)
81 }
79
80 edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)}
81 @can = {:edit => edit_allowed, :delete => edit_allowed}
82 82 @back = back_url
83 83
84 84 @options_by_custom_field = {}
85 85 if @can[:edit]
86 86 custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
87 87 custom_fields.each do |field|
88 88 values = field.possible_values_options(@projects)
89 89 if values.present?
90 90 @options_by_custom_field[field] = values
91 91 end
92 92 end
93 93 end
94 94
95 95 render :layout => false
96 96 end
97 97 end
@@ -1,280 +1,281
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 TimelogController < ApplicationController
19 19 menu_item :issues
20 20
21 21 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 23 before_filter :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
24 24
25 25 before_filter :find_optional_project, :only => [:new, :create, :index, :report]
26 26 before_filter :authorize_global, :only => [:new, :create, :index, :report]
27 27
28 28 accept_rss_auth :index
29 29 accept_api_auth :index, :show, :create, :update, :destroy
30 30
31 31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 32
33 33 helper :sort
34 34 include SortHelper
35 35 helper :issues
36 36 include TimelogHelper
37 37 helper :custom_fields
38 38 include CustomFieldsHelper
39 39 helper :queries
40 40 include QueriesHelper
41 41
42 42 def index
43 43 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
44 44
45 45 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
46 46 sort_update(@query.sortable_columns)
47 47 scope = time_entry_scope(:order => sort_clause).
48 48 includes(:project, :user, :issue).
49 49 preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
50 50
51 51 respond_to do |format|
52 52 format.html {
53 53 @entry_count = scope.count
54 54 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
55 55 @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
56 56 @total_hours = scope.sum(:hours).to_f
57 57
58 58 render :layout => !request.xhr?
59 59 }
60 60 format.api {
61 61 @entry_count = scope.count
62 62 @offset, @limit = api_offset_and_limit
63 63 @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).to_a
64 64 }
65 65 format.atom {
66 66 entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").to_a
67 67 render_feed(entries, :title => l(:label_spent_time))
68 68 }
69 69 format.csv {
70 70 # Export all entries
71 71 @entries = scope.to_a
72 72 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
73 73 }
74 74 end
75 75 end
76 76
77 77 def report
78 78 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
79 79 scope = time_entry_scope
80 80
81 81 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
82 82
83 83 respond_to do |format|
84 84 format.html { render :layout => !request.xhr? }
85 85 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
86 86 end
87 87 end
88 88
89 89 def show
90 90 respond_to do |format|
91 91 # TODO: Implement html response
92 92 format.html { render :nothing => true, :status => 406 }
93 93 format.api
94 94 end
95 95 end
96 96
97 97 def new
98 98 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
99 99 @time_entry.safe_attributes = params[:time_entry]
100 100 end
101 101
102 102 def create
103 103 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
104 104 @time_entry.safe_attributes = params[:time_entry]
105 105 if @time_entry.project && !User.current.allowed_to?(:log_time, @time_entry.project)
106 106 render_403
107 107 return
108 108 end
109 109
110 110 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
111 111
112 112 if @time_entry.save
113 113 respond_to do |format|
114 114 format.html {
115 115 flash[:notice] = l(:notice_successful_create)
116 116 if params[:continue]
117 117 options = {
118 118 :time_entry => {
119 119 :project_id => params[:time_entry][:project_id],
120 120 :issue_id => @time_entry.issue_id,
121 121 :activity_id => @time_entry.activity_id
122 122 },
123 123 :back_url => params[:back_url]
124 124 }
125 125 if params[:project_id] && @time_entry.project
126 126 redirect_to new_project_time_entry_path(@time_entry.project, options)
127 127 elsif params[:issue_id] && @time_entry.issue
128 128 redirect_to new_issue_time_entry_path(@time_entry.issue, options)
129 129 else
130 130 redirect_to new_time_entry_path(options)
131 131 end
132 132 else
133 133 redirect_back_or_default project_time_entries_path(@time_entry.project)
134 134 end
135 135 }
136 136 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
137 137 end
138 138 else
139 139 respond_to do |format|
140 140 format.html { render :action => 'new' }
141 141 format.api { render_validation_errors(@time_entry) }
142 142 end
143 143 end
144 144 end
145 145
146 146 def edit
147 147 @time_entry.safe_attributes = params[:time_entry]
148 148 end
149 149
150 150 def update
151 151 @time_entry.safe_attributes = params[:time_entry]
152 152
153 153 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
154 154
155 155 if @time_entry.save
156 156 respond_to do |format|
157 157 format.html {
158 158 flash[:notice] = l(:notice_successful_update)
159 159 redirect_back_or_default project_time_entries_path(@time_entry.project)
160 160 }
161 161 format.api { render_api_ok }
162 162 end
163 163 else
164 164 respond_to do |format|
165 165 format.html { render :action => 'edit' }
166 166 format.api { render_validation_errors(@time_entry) }
167 167 end
168 168 end
169 169 end
170 170
171 171 def bulk_edit
172 172 @available_activities = TimeEntryActivity.shared.active
173 173 @custom_fields = TimeEntry.first.available_custom_fields
174 174 end
175 175
176 176 def bulk_update
177 177 attributes = parse_params_for_bulk_time_entry_attributes(params)
178 178
179 179 unsaved_time_entry_ids = []
180 180 @time_entries.each do |time_entry|
181 181 time_entry.reload
182 182 time_entry.safe_attributes = attributes
183 183 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
184 184 unless time_entry.save
185 185 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info?
186 186 # Keep unsaved time_entry ids to display them in flash error
187 187 unsaved_time_entry_ids << time_entry.id
188 188 end
189 189 end
190 190 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
191 191 redirect_back_or_default project_time_entries_path(@projects.first)
192 192 end
193 193
194 194 def destroy
195 195 destroyed = TimeEntry.transaction do
196 196 @time_entries.each do |t|
197 197 unless t.destroy && t.destroyed?
198 198 raise ActiveRecord::Rollback
199 199 end
200 200 end
201 201 end
202 202
203 203 respond_to do |format|
204 204 format.html {
205 205 if destroyed
206 206 flash[:notice] = l(:notice_successful_delete)
207 207 else
208 208 flash[:error] = l(:notice_unable_delete_time_entry)
209 209 end
210 210 redirect_back_or_default project_time_entries_path(@projects.first)
211 211 }
212 212 format.api {
213 213 if destroyed
214 214 render_api_ok
215 215 else
216 216 render_validation_errors(@time_entries)
217 217 end
218 218 }
219 219 end
220 220 end
221 221
222 222 private
223 223 def find_time_entry
224 224 @time_entry = TimeEntry.find(params[:id])
225 225 unless @time_entry.editable_by?(User.current)
226 226 render_403
227 227 return false
228 228 end
229 229 @project = @time_entry.project
230 230 rescue ActiveRecord::RecordNotFound
231 231 render_404
232 232 end
233 233
234 234 def find_time_entries
235 235 @time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).to_a
236 236 raise ActiveRecord::RecordNotFound if @time_entries.empty?
237 raise Unauthorized unless @time_entries.all? {|t| t.editable_by?(User.current)}
237 238 @projects = @time_entries.collect(&:project).compact.uniq
238 239 @project = @projects.first if @projects.size == 1
239 240 rescue ActiveRecord::RecordNotFound
240 241 render_404
241 242 end
242 243
243 244 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
244 245 if unsaved_time_entry_ids.empty?
245 246 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
246 247 else
247 248 flash[:error] = l(:notice_failed_to_save_time_entries,
248 249 :count => unsaved_time_entry_ids.size,
249 250 :total => time_entries.size,
250 251 :ids => '#' + unsaved_time_entry_ids.join(', #'))
251 252 end
252 253 end
253 254
254 255 def find_optional_project
255 256 if params[:issue_id].present?
256 257 @issue = Issue.find(params[:issue_id])
257 258 @project = @issue.project
258 259 elsif params[:project_id].present?
259 260 @project = Project.find(params[:project_id])
260 261 end
261 262 rescue ActiveRecord::RecordNotFound
262 263 render_404
263 264 end
264 265
265 266 # Returns the TimeEntry scope for index and report actions
266 267 def time_entry_scope(options={})
267 268 scope = @query.results_scope(options)
268 269 if @issue
269 270 scope = scope.on_issue(@issue)
270 271 end
271 272 scope
272 273 end
273 274
274 275 def parse_params_for_bulk_time_entry_attributes(params)
275 276 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
276 277 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
277 278 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
278 279 attributes
279 280 end
280 281 end
@@ -1,288 +1,300
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 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class ContextMenusControllerTest < ActionController::TestCase
21 21 fixtures :projects,
22 22 :trackers,
23 23 :projects_trackers,
24 24 :roles,
25 25 :member_roles,
26 26 :members,
27 27 :enabled_modules,
28 28 :workflows,
29 29 :journals, :journal_details,
30 30 :versions,
31 31 :issues, :issue_statuses, :issue_categories,
32 32 :users,
33 33 :enumerations,
34 34 :time_entries
35 35
36 36 def test_context_menu_one_issue
37 37 @request.session[:user_id] = 2
38 38 get :issues, :ids => [1]
39 39 assert_response :success
40 40 assert_template 'context_menus/issues'
41 41
42 42 assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit'
43 43 assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy'
44 44 assert_select 'a.icon-del[href=?]', '/issues?ids%5B%5D=1', :text => 'Delete'
45 45
46 46 # Statuses
47 47 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', :text => 'Closed'
48 48 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', :text => 'Immediate'
49 49 # No inactive priorities
50 50 assert_select 'a', :text => /Inactive Priority/, :count => 0
51 51 # Versions
52 52 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', :text => '2.0'
53 53 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
54 54 # Assignees
55 55 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
56 56 end
57 57
58 58 def test_context_menu_one_issue_by_anonymous
59 59 with_settings :default_language => 'en' do
60 60 get :issues, :ids => [1]
61 61 assert_response :success
62 62 assert_template 'context_menus/issues'
63 63 assert_select 'a.icon-del.disabled[href="#"]', :text => 'Delete'
64 64 end
65 65 end
66 66
67 67 def test_context_menu_multiple_issues_of_same_project
68 68 @request.session[:user_id] = 2
69 69 get :issues, :ids => [1, 2]
70 70 assert_response :success
71 71 assert_template 'context_menus/issues'
72 72 assert_not_nil assigns(:issues)
73 73 assert_equal [1, 2], assigns(:issues).map(&:id).sort
74 74
75 75 ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&')
76 76
77 77 assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit'
78 78 assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy'
79 79 assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete'
80 80
81 81 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed'
82 82 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate'
83 83 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", :text => 'Dave Lopper'
84 84 end
85 85
86 86 def test_context_menu_multiple_issues_of_different_projects
87 87 @request.session[:user_id] = 2
88 88 get :issues, :ids => [1, 2, 6]
89 89 assert_response :success
90 90 assert_template 'context_menus/issues'
91 91 assert_not_nil assigns(:issues)
92 92 assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort
93 93
94 94 ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&')
95 95
96 96 assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit'
97 97 assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete'
98 98
99 99 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed'
100 100 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate'
101 101 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", :text => 'John Smith'
102 102 end
103 103
104 104 def test_context_menu_should_include_list_custom_fields
105 105 field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
106 106 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
107 107 @request.session[:user_id] = 2
108 108 get :issues, :ids => [1]
109 109
110 110 assert_select "li.cf_#{field.id}" do
111 111 assert_select 'a[href="#"]', :text => 'List'
112 112 assert_select 'ul' do
113 113 assert_select 'a', 3
114 114 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
115 115 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
116 116 end
117 117 end
118 118 end
119 119
120 120 def test_context_menu_should_not_include_null_value_for_required_custom_fields
121 121 field = IssueCustomField.create!(:name => 'List', :is_required => true, :field_format => 'list',
122 122 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
123 123 @request.session[:user_id] = 2
124 124 get :issues, :ids => [1, 2]
125 125
126 126 assert_select "li.cf_#{field.id}" do
127 127 assert_select 'a[href="#"]', :text => 'List'
128 128 assert_select 'ul' do
129 129 assert_select 'a', 2
130 130 assert_select 'a', :text => 'none', :count => 0
131 131 end
132 132 end
133 133 end
134 134
135 135 def test_context_menu_on_single_issue_should_select_current_custom_field_value
136 136 field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
137 137 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
138 138 issue = Issue.find(1)
139 139 issue.custom_field_values = {field.id => 'Bar'}
140 140 issue.save!
141 141 @request.session[:user_id] = 2
142 142 get :issues, :ids => [1]
143 143
144 144 assert_select "li.cf_#{field.id}" do
145 145 assert_select 'a[href="#"]', :text => 'List'
146 146 assert_select 'ul' do
147 147 assert_select 'a', 3
148 148 assert_select 'a.icon-checked', :text => 'Bar'
149 149 end
150 150 end
151 151 end
152 152
153 153 def test_context_menu_should_include_bool_custom_fields
154 154 field = IssueCustomField.create!(:name => 'Bool', :field_format => 'bool',
155 155 :is_for_all => true, :tracker_ids => [1, 2, 3])
156 156 @request.session[:user_id] = 2
157 157 get :issues, :ids => [1]
158 158
159 159 assert_select "li.cf_#{field.id}" do
160 160 assert_select 'a[href="#"]', :text => 'Bool'
161 161 assert_select 'ul' do
162 162 assert_select 'a', 3
163 163 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=0", :text => 'No'
164 164 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1", :text => 'Yes'
165 165 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
166 166 end
167 167 end
168 168 end
169 169
170 170 def test_context_menu_should_include_user_custom_fields
171 171 field = IssueCustomField.create!(:name => 'User', :field_format => 'user',
172 172 :is_for_all => true, :tracker_ids => [1, 2, 3])
173 173 @request.session[:user_id] = 2
174 174 get :issues, :ids => [1]
175 175
176 176 assert_select "li.cf_#{field.id}" do
177 177 assert_select 'a[href="#"]', :text => 'User'
178 178 assert_select 'ul' do
179 179 assert_select 'a', Project.find(1).members.count + 1
180 180 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2", :text => 'John Smith'
181 181 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
182 182 end
183 183 end
184 184 end
185 185
186 186 def test_context_menu_should_include_version_custom_fields
187 187 field = IssueCustomField.create!(:name => 'Version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1, 2, 3])
188 188 @request.session[:user_id] = 2
189 189 get :issues, :ids => [1]
190 190
191 191 assert_select "li.cf_#{field.id}" do
192 192 assert_select 'a[href="#"]', :text => 'Version'
193 193 assert_select 'ul' do
194 194 assert_select 'a', Project.find(1).shared_versions.count + 1
195 195 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3", :text => '2.0'
196 196 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
197 197 end
198 198 end
199 199 end
200 200
201 201 def test_context_menu_should_show_enabled_custom_fields_for_the_role_only
202 202 enabled_cf = IssueCustomField.generate!(:field_format => 'bool', :is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
203 203 disabled_cf = IssueCustomField.generate!(:field_format => 'bool', :is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
204 204 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
205 205
206 206 @request.session[:user_id] = 2
207 207 get :issues, :ids => [issue.id]
208 208
209 209 assert_select "li.cf_#{enabled_cf.id}"
210 210 assert_select "li.cf_#{disabled_cf.id}", 0
211 211 end
212 212
213 213 def test_context_menu_by_assignable_user_should_include_assigned_to_me_link
214 214 @request.session[:user_id] = 2
215 215 get :issues, :ids => [1]
216 216 assert_response :success
217 217 assert_template 'context_menus/issues'
218 218
219 219 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', :text => / me /
220 220 end
221 221
222 222 def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects
223 223 @request.session[:user_id] = 2
224 224 version = Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1)
225 225
226 226 get :issues, :ids => [1, 4]
227 227 assert_response :success
228 228 assert_template 'context_menus/issues'
229 229
230 230 assert_include version, assigns(:versions)
231 231 assert_select 'a', :text => 'eCookbook - Shared'
232 232 end
233 233
234 234 def test_context_menu_with_issue_that_is_not_visible_should_fail
235 235 get :issues, :ids => [1, 4] # issue 4 is not visible
236 236 assert_response 302
237 237 end
238 238
239 239 def test_should_respond_with_404_without_ids
240 240 get :issues
241 241 assert_response 404
242 242 end
243 243
244 244 def test_time_entries_context_menu
245 245 @request.session[:user_id] = 2
246 246 get :time_entries, :ids => [1, 2]
247 247 assert_response :success
248 248 assert_template 'context_menus/time_entries'
249 249
250 250 assert_select 'a:not(.disabled)', :text => 'Edit'
251 251 end
252 252
253 253 def test_context_menu_for_one_time_entry
254 254 @request.session[:user_id] = 2
255 255 get :time_entries, :ids => [1]
256 256 assert_response :success
257 257 assert_template 'context_menus/time_entries'
258 258
259 259 assert_select 'a:not(.disabled)', :text => 'Edit'
260 260 end
261 261
262 262 def test_time_entries_context_menu_should_include_custom_fields
263 263 field = TimeEntryCustomField.generate!(:name => "Field", :field_format => "list", :possible_values => ["foo", "bar"])
264 264
265 265 @request.session[:user_id] = 2
266 266 get :time_entries, :ids => [1, 2]
267 267 assert_response :success
268 268 assert_select "li.cf_#{field.id}" do
269 269 assert_select 'a[href="#"]', :text => "Field"
270 270 assert_select 'ul' do
271 271 assert_select 'a', 3
272 272 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=foo", :text => 'foo'
273 273 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=bar", :text => 'bar'
274 274 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
275 275 end
276 276 end
277 277 end
278 278
279 def test_time_entries_context_menu_with_edit_own_time_entries_permission
280 @request.session[:user_id] = 2
281 Role.find_by_name('Manager').remove_permission! :edit_time_entries
282 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
283 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
284
285 get :time_entries, :ids => ids
286 assert_response :success
287 assert_template 'context_menus/time_entries'
288 assert_select 'a:not(.disabled)', :text => 'Edit'
289 end
290
279 291 def test_time_entries_context_menu_without_edit_permission
280 292 @request.session[:user_id] = 2
281 293 Role.find_by_name('Manager').remove_permission! :edit_time_entries
282 294
283 295 get :time_entries, :ids => [1, 2]
284 296 assert_response :success
285 297 assert_template 'context_menus/time_entries'
286 298 assert_select 'a.disabled', :text => 'Edit'
287 299 end
288 300 end
@@ -1,773 +1,802
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 215 def test_create_and_continue_at_project_level
216 216 @request.session[:user_id] = 2
217 217 assert_difference 'TimeEntry.count' do
218 218 post :create, :time_entry => {:project_id => '1',
219 219 :activity_id => '11',
220 220 :issue_id => '',
221 221 :spent_on => '2008-03-14',
222 222 :hours => '7.3'},
223 223 :continue => '1'
224 224 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
225 225 end
226 226 end
227 227
228 228 def test_create_and_continue_at_issue_level
229 229 @request.session[:user_id] = 2
230 230 assert_difference 'TimeEntry.count' do
231 231 post :create, :time_entry => {:project_id => '',
232 232 :activity_id => '11',
233 233 :issue_id => '1',
234 234 :spent_on => '2008-03-14',
235 235 :hours => '7.3'},
236 236 :continue => '1'
237 237 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
238 238 end
239 239 end
240 240
241 241 def test_create_and_continue_with_project_id
242 242 @request.session[:user_id] = 2
243 243 assert_difference 'TimeEntry.count' do
244 244 post :create, :project_id => 1,
245 245 :time_entry => {:activity_id => '11',
246 246 :issue_id => '',
247 247 :spent_on => '2008-03-14',
248 248 :hours => '7.3'},
249 249 :continue => '1'
250 250 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 251 end
252 252 end
253 253
254 254 def test_create_and_continue_with_issue_id
255 255 @request.session[:user_id] = 2
256 256 assert_difference 'TimeEntry.count' do
257 257 post :create, :issue_id => 1,
258 258 :time_entry => {:activity_id => '11',
259 259 :issue_id => '1',
260 260 :spent_on => '2008-03-14',
261 261 :hours => '7.3'},
262 262 :continue => '1'
263 263 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 264 end
265 265 end
266 266
267 267 def test_create_without_log_time_permission_should_be_denied
268 268 @request.session[:user_id] = 2
269 269 Role.find_by_name('Manager').remove_permission! :log_time
270 270 post :create, :project_id => 1,
271 271 :time_entry => {:activity_id => '11',
272 272 :issue_id => '',
273 273 :spent_on => '2008-03-14',
274 274 :hours => '7.3'}
275 275
276 276 assert_response 403
277 277 end
278 278
279 279 def test_create_without_project_and_issue_should_fail
280 280 @request.session[:user_id] = 2
281 281 post :create, :time_entry => {:issue_id => ''}
282 282
283 283 assert_response :success
284 284 assert_template 'new'
285 285 end
286 286
287 287 def test_create_with_failure
288 288 @request.session[:user_id] = 2
289 289 post :create, :project_id => 1,
290 290 :time_entry => {:activity_id => '',
291 291 :issue_id => '',
292 292 :spent_on => '2008-03-14',
293 293 :hours => '7.3'}
294 294
295 295 assert_response :success
296 296 assert_template 'new'
297 297 end
298 298
299 299 def test_create_without_project
300 300 @request.session[:user_id] = 2
301 301 assert_difference 'TimeEntry.count' do
302 302 post :create, :time_entry => {:project_id => '1',
303 303 :activity_id => '11',
304 304 :issue_id => '',
305 305 :spent_on => '2008-03-14',
306 306 :hours => '7.3'}
307 307 end
308 308
309 309 assert_redirected_to '/projects/ecookbook/time_entries'
310 310 time_entry = TimeEntry.order('id DESC').first
311 311 assert_equal 1, time_entry.project_id
312 312 end
313 313
314 314 def test_create_without_project_should_fail_with_issue_not_inside_project
315 315 @request.session[:user_id] = 2
316 316 assert_no_difference 'TimeEntry.count' do
317 317 post :create, :time_entry => {:project_id => '1',
318 318 :activity_id => '11',
319 319 :issue_id => '5',
320 320 :spent_on => '2008-03-14',
321 321 :hours => '7.3'}
322 322 end
323 323
324 324 assert_response :success
325 325 assert assigns(:time_entry).errors[:issue_id].present?
326 326 end
327 327
328 328 def test_create_without_project_should_deny_without_permission
329 329 @request.session[:user_id] = 2
330 330 Project.find(3).disable_module!(:time_tracking)
331 331
332 332 assert_no_difference 'TimeEntry.count' do
333 333 post :create, :time_entry => {:project_id => '3',
334 334 :activity_id => '11',
335 335 :issue_id => '',
336 336 :spent_on => '2008-03-14',
337 337 :hours => '7.3'}
338 338 end
339 339
340 340 assert_response 403
341 341 end
342 342
343 343 def test_create_without_project_with_failure
344 344 @request.session[:user_id] = 2
345 345 assert_no_difference 'TimeEntry.count' do
346 346 post :create, :time_entry => {:project_id => '1',
347 347 :activity_id => '11',
348 348 :issue_id => '',
349 349 :spent_on => '2008-03-14',
350 350 :hours => ''}
351 351 end
352 352
353 353 assert_response :success
354 354 assert_select 'select[name=?]', 'time_entry[project_id]' do
355 355 assert_select 'option[value="1"][selected=selected]'
356 356 end
357 357 end
358 358
359 359 def test_update
360 360 entry = TimeEntry.find(1)
361 361 assert_equal 1, entry.issue_id
362 362 assert_equal 2, entry.user_id
363 363
364 364 @request.session[:user_id] = 1
365 365 put :update, :id => 1,
366 366 :time_entry => {:issue_id => '2',
367 367 :hours => '8'}
368 368 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
369 369 entry.reload
370 370
371 371 assert_equal 8, entry.hours
372 372 assert_equal 2, entry.issue_id
373 373 assert_equal 2, entry.user_id
374 374 end
375 375
376 376 def test_update_should_allow_to_change_issue_to_another_project
377 377 entry = TimeEntry.generate!(:issue_id => 1)
378 378
379 379 @request.session[:user_id] = 1
380 380 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
381 381 assert_response 302
382 382 entry.reload
383 383
384 384 assert_equal 5, entry.issue_id
385 385 assert_equal 3, entry.project_id
386 386 end
387 387
388 388 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
389 389 entry = TimeEntry.generate!(:issue_id => 1)
390 390 Project.find(3).disable_module!(:time_tracking)
391 391
392 392 @request.session[:user_id] = 1
393 393 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
394 394 assert_response 200
395 395 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
396 396 end
397 397
398 398 def test_get_bulk_edit
399 399 @request.session[:user_id] = 2
400 400 get :bulk_edit, :ids => [1, 2]
401 401 assert_response :success
402 402 assert_template 'bulk_edit'
403 403
404 404 assert_select 'ul#bulk-selection' do
405 405 assert_select 'li', 2
406 406 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
407 407 end
408 408
409 409 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
410 410 # System wide custom field
411 411 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
412 412
413 413 # Activities
414 414 assert_select 'select[name=?]', 'time_entry[activity_id]' do
415 415 assert_select 'option[value=""]', :text => '(No change)'
416 416 assert_select 'option[value="9"]', :text => 'Design'
417 417 end
418 418 end
419 419 end
420 420
421 421 def test_get_bulk_edit_on_different_projects
422 422 @request.session[:user_id] = 2
423 423 get :bulk_edit, :ids => [1, 2, 6]
424 424 assert_response :success
425 425 assert_template 'bulk_edit'
426 426 end
427 427
428 def test_bulk_edit_with_edit_own_time_entries_permission
429 @request.session[:user_id] = 2
430 Role.find_by_name('Manager').remove_permission! :edit_time_entries
431 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
432 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
433
434 get :bulk_edit, :ids => ids
435 assert_response :success
436 end
437
428 438 def test_bulk_update
429 439 @request.session[:user_id] = 2
430 440 # update time entry activity
431 441 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
432 442
433 443 assert_response 302
434 444 # check that the issues were updated
435 445 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
436 446 end
437 447
438 448 def test_bulk_update_with_failure
439 449 @request.session[:user_id] = 2
440 450 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
441 451
442 452 assert_response 302
443 453 assert_match /Failed to save 2 time entrie/, flash[:error]
444 454 end
445 455
446 456 def test_bulk_update_on_different_projects
447 457 @request.session[:user_id] = 2
448 458 # makes user a manager on the other project
449 459 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
450 460
451 461 # update time entry activity
452 462 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
453 463
454 464 assert_response 302
455 465 # check that the issues were updated
456 466 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
457 467 end
458 468
459 469 def test_bulk_update_on_different_projects_without_rights
460 470 @request.session[:user_id] = 3
461 471 user = User.find(3)
462 472 action = { :controller => "timelog", :action => "bulk_update" }
463 473 assert user.allowed_to?(action, TimeEntry.find(1).project)
464 474 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
465 475 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
466 476 assert_response 403
467 477 end
468 478
479 def test_bulk_update_with_edit_own_time_entries_permission
480 @request.session[:user_id] = 2
481 Role.find_by_name('Manager').remove_permission! :edit_time_entries
482 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
483 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
484
485 post :bulk_update, :ids => ids, :time_entry => { :activity_id => 9 }
486 assert_response 302
487 end
488
489 def test_bulk_update_with_edit_own_time_entries_permissions_should_be_denied_for_time_entries_of_other_user
490 @request.session[:user_id] = 2
491 Role.find_by_name('Manager').remove_permission! :edit_time_entries
492 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
493
494 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9 }
495 assert_response 403
496 end
497
469 498 def test_bulk_update_custom_field
470 499 @request.session[:user_id] = 2
471 500 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
472 501
473 502 assert_response 302
474 503 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
475 504 end
476 505
477 506 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
478 507 @request.session[:user_id] = 2
479 508 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
480 509
481 510 assert_response :redirect
482 511 assert_redirected_to '/time_entries'
483 512 end
484 513
485 514 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
486 515 @request.session[:user_id] = 2
487 516 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
488 517
489 518 assert_response :redirect
490 519 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
491 520 end
492 521
493 522 def test_post_bulk_update_without_edit_permission_should_be_denied
494 523 @request.session[:user_id] = 2
495 524 Role.find_by_name('Manager').remove_permission! :edit_time_entries
496 525 post :bulk_update, :ids => [1,2]
497 526
498 527 assert_response 403
499 528 end
500 529
501 530 def test_destroy
502 531 @request.session[:user_id] = 2
503 532 delete :destroy, :id => 1
504 533 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
505 534 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
506 535 assert_nil TimeEntry.find_by_id(1)
507 536 end
508 537
509 538 def test_destroy_should_fail
510 539 # simulate that this fails (e.g. due to a plugin), see #5700
511 540 TimeEntry.any_instance.expects(:destroy).returns(false)
512 541
513 542 @request.session[:user_id] = 2
514 543 delete :destroy, :id => 1
515 544 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
516 545 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
517 546 assert_not_nil TimeEntry.find_by_id(1)
518 547 end
519 548
520 549 def test_index_all_projects
521 550 get :index
522 551 assert_response :success
523 552 assert_template 'index'
524 553 assert_not_nil assigns(:total_hours)
525 554 assert_equal "162.90", "%.2f" % assigns(:total_hours)
526 555 assert_select 'form#query_form[action=?]', '/time_entries'
527 556 end
528 557
529 558 def test_index_all_projects_should_show_log_time_link
530 559 @request.session[:user_id] = 2
531 560 get :index
532 561 assert_response :success
533 562 assert_template 'index'
534 563 assert_select 'a[href=?]', '/time_entries/new', :text => /Log time/
535 564 end
536 565
537 566 def test_index_my_spent_time
538 567 @request.session[:user_id] = 2
539 568 get :index, :user_id => 'me'
540 569 assert_response :success
541 570 assert_template 'index'
542 571 assert assigns(:entries).all? {|entry| entry.user_id == 2}
543 572 end
544 573
545 574 def test_index_at_project_level
546 575 get :index, :project_id => 'ecookbook'
547 576 assert_response :success
548 577 assert_template 'index'
549 578 assert_not_nil assigns(:entries)
550 579 assert_equal 4, assigns(:entries).size
551 580 # project and subproject
552 581 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
553 582 assert_not_nil assigns(:total_hours)
554 583 assert_equal "162.90", "%.2f" % assigns(:total_hours)
555 584 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
556 585 end
557 586
558 587 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
559 588 entry = TimeEntry.generate!(:project => Project.find(3))
560 589
561 590 with_settings :display_subprojects_issues => '0' do
562 591 get :index, :project_id => 'ecookbook'
563 592 assert_response :success
564 593 assert_template 'index'
565 594 assert_not_include entry, assigns(:entries)
566 595 end
567 596 end
568 597
569 598 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
570 599 entry = TimeEntry.generate!(:project => Project.find(3))
571 600
572 601 with_settings :display_subprojects_issues => '0' do
573 602 get :index, :project_id => 'ecookbook', :subproject_id => 3
574 603 assert_response :success
575 604 assert_template 'index'
576 605 assert_include entry, assigns(:entries)
577 606 end
578 607 end
579 608
580 609 def test_index_at_project_level_with_date_range
581 610 get :index, :project_id => 'ecookbook',
582 611 :f => ['spent_on'],
583 612 :op => {'spent_on' => '><'},
584 613 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
585 614 assert_response :success
586 615 assert_template 'index'
587 616 assert_not_nil assigns(:entries)
588 617 assert_equal 3, assigns(:entries).size
589 618 assert_not_nil assigns(:total_hours)
590 619 assert_equal "12.90", "%.2f" % assigns(:total_hours)
591 620 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
592 621 end
593 622
594 623 def test_index_at_project_level_with_date_range_using_from_and_to_params
595 624 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
596 625 assert_response :success
597 626 assert_template 'index'
598 627 assert_not_nil assigns(:entries)
599 628 assert_equal 3, assigns(:entries).size
600 629 assert_not_nil assigns(:total_hours)
601 630 assert_equal "12.90", "%.2f" % assigns(:total_hours)
602 631 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
603 632 end
604 633
605 634 def test_index_at_project_level_with_period
606 635 get :index, :project_id => 'ecookbook',
607 636 :f => ['spent_on'],
608 637 :op => {'spent_on' => '>t-'},
609 638 :v => {'spent_on' => ['7']}
610 639 assert_response :success
611 640 assert_template 'index'
612 641 assert_not_nil assigns(:entries)
613 642 assert_not_nil assigns(:total_hours)
614 643 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
615 644 end
616 645
617 646 def test_index_at_issue_level
618 647 get :index, :issue_id => 1
619 648 assert_response :success
620 649 assert_template 'index'
621 650 assert_not_nil assigns(:entries)
622 651 assert_equal 2, assigns(:entries).size
623 652 assert_not_nil assigns(:total_hours)
624 653 assert_equal 154.25, assigns(:total_hours)
625 654 # display all time
626 655 assert_nil assigns(:from)
627 656 assert_nil assigns(:to)
628 657 assert_select 'form#query_form[action=?]', '/issues/1/time_entries'
629 658 end
630 659
631 660 def test_index_should_sort_by_spent_on_and_created_on
632 661 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)
633 662 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)
634 663 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)
635 664
636 665 get :index, :project_id => 1,
637 666 :f => ['spent_on'],
638 667 :op => {'spent_on' => '><'},
639 668 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
640 669 assert_response :success
641 670 assert_equal [t2, t1, t3], assigns(:entries)
642 671
643 672 get :index, :project_id => 1,
644 673 :f => ['spent_on'],
645 674 :op => {'spent_on' => '><'},
646 675 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
647 676 :sort => 'spent_on'
648 677 assert_response :success
649 678 assert_equal [t3, t1, t2], assigns(:entries)
650 679 end
651 680
652 681 def test_index_with_filter_on_issue_custom_field
653 682 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
654 683 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
655 684
656 685 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
657 686 assert_response :success
658 687 assert_equal [entry], assigns(:entries)
659 688 end
660 689
661 690 def test_index_with_issue_custom_field_column
662 691 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
663 692 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
664 693
665 694 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
666 695 assert_response :success
667 696 assert_include :'issue.cf_2', assigns(:query).column_names
668 697 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
669 698 end
670 699
671 700 def test_index_with_time_entry_custom_field_column
672 701 field = TimeEntryCustomField.generate!(:field_format => 'string')
673 702 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
674 703 field_name = "cf_#{field.id}"
675 704
676 705 get :index, :c => ["hours", field_name]
677 706 assert_response :success
678 707 assert_include field_name.to_sym, assigns(:query).column_names
679 708 assert_select "td.#{field_name}", :text => 'CF Value'
680 709 end
681 710
682 711 def test_index_with_time_entry_custom_field_sorting
683 712 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
684 713 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
685 714 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
686 715 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
687 716 field_name = "cf_#{field.id}"
688 717
689 718 get :index, :c => ["hours", field_name], :sort => field_name
690 719 assert_response :success
691 720 assert_include field_name.to_sym, assigns(:query).column_names
692 721 assert_select "th a.sort", :text => 'String Field'
693 722
694 723 # Make sure that values are properly sorted
695 724 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
696 725 assert_equal 3, values.size
697 726 assert_equal values.sort, values
698 727 end
699 728
700 729 def test_index_atom_feed
701 730 get :index, :project_id => 1, :format => 'atom'
702 731 assert_response :success
703 732 assert_equal 'application/atom+xml', @response.content_type
704 733 assert_not_nil assigns(:items)
705 734 assert assigns(:items).first.is_a?(TimeEntry)
706 735 end
707 736
708 737 def test_index_at_project_level_should_include_csv_export_dialog
709 738 get :index, :project_id => 'ecookbook',
710 739 :f => ['spent_on'],
711 740 :op => {'spent_on' => '>='},
712 741 :v => {'spent_on' => ['2007-04-01']},
713 742 :c => ['spent_on', 'user']
714 743 assert_response :success
715 744
716 745 assert_select '#csv-export-options' do
717 746 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
718 747 # filter
719 748 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
720 749 assert_select 'input[name=?][value=?]', 'op[spent_on]', '>='
721 750 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
722 751 # columns
723 752 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
724 753 assert_select 'input[name=?][value=?]', 'c[]', 'user'
725 754 assert_select 'input[name=?]', 'c[]', 2
726 755 end
727 756 end
728 757 end
729 758
730 759 def test_index_cross_project_should_include_csv_export_dialog
731 760 get :index
732 761 assert_response :success
733 762
734 763 assert_select '#csv-export-options' do
735 764 assert_select 'form[action=?][method=get]', '/time_entries.csv'
736 765 end
737 766 end
738 767
739 768 def test_index_at_issue_level_should_include_csv_export_dialog
740 769 get :index, :issue_id => 3
741 770 assert_response :success
742 771
743 772 assert_select '#csv-export-options' do
744 773 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
745 774 end
746 775 end
747 776
748 777 def test_index_csv_all_projects
749 778 with_settings :date_format => '%m/%d/%Y' do
750 779 get :index, :format => 'csv'
751 780 assert_response :success
752 781 assert_equal 'text/csv; header=present', response.content_type
753 782 end
754 783 end
755 784
756 785 def test_index_csv
757 786 with_settings :date_format => '%m/%d/%Y' do
758 787 get :index, :project_id => 1, :format => 'csv'
759 788 assert_response :success
760 789 assert_equal 'text/csv; header=present', response.content_type
761 790 end
762 791 end
763 792
764 793 def test_index_csv_should_fill_issue_column_with_tracker_id_and_subject
765 794 issue = Issue.find(1)
766 795 entry = TimeEntry.generate!(:issue => issue, :comments => "Issue column content test")
767 796
768 797 get :index, :format => 'csv'
769 798 line = response.body.split("\n").detect {|l| l.include?(entry.comments)}
770 799 assert_not_nil line
771 800 assert_include "#{issue.tracker} #1: #{issue.subject}", line
772 801 end
773 802 end
General Comments 0
You need to be logged in to leave comments. Login now