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