##// END OF EJS Templates
Fixed: private queries should not be accessible to other users (#8729)....
Jean-Philippe Lang -
r6043:8914d323ee14
parent child
Show More
@@ -1,487 +1,490
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'uri'
18 require 'uri'
19 require 'cgi'
19 require 'cgi'
20
20
21 class Unauthorized < Exception; end
22
21 class ApplicationController < ActionController::Base
23 class ApplicationController < ActionController::Base
22 include Redmine::I18n
24 include Redmine::I18n
23
25
24 layout 'base'
26 layout 'base'
25 exempt_from_layout 'builder', 'rsb'
27 exempt_from_layout 'builder', 'rsb'
26
28
27 # Remove broken cookie after upgrade from 0.8.x (#4292)
29 # Remove broken cookie after upgrade from 0.8.x (#4292)
28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
30 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
29 # TODO: remove it when Rails is fixed
31 # TODO: remove it when Rails is fixed
30 before_filter :delete_broken_cookies
32 before_filter :delete_broken_cookies
31 def delete_broken_cookies
33 def delete_broken_cookies
32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
34 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
33 cookies.delete '_redmine_session'
35 cookies.delete '_redmine_session'
34 redirect_to home_path
36 redirect_to home_path
35 return false
37 return false
36 end
38 end
37 end
39 end
38
40
39 before_filter :user_setup, :check_if_login_required, :set_localization
41 before_filter :user_setup, :check_if_login_required, :set_localization
40 filter_parameter_logging :password
42 filter_parameter_logging :password
41 protect_from_forgery
43 protect_from_forgery
42
44
43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
45 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
46 rescue_from ::Unauthorized, :with => :deny_access
44
47
45 include Redmine::Search::Controller
48 include Redmine::Search::Controller
46 include Redmine::MenuManager::MenuController
49 include Redmine::MenuManager::MenuController
47 helper Redmine::MenuManager::MenuHelper
50 helper Redmine::MenuManager::MenuHelper
48
51
49 Redmine::Scm::Base.all.each do |scm|
52 Redmine::Scm::Base.all.each do |scm|
50 require_dependency "repository/#{scm.underscore}"
53 require_dependency "repository/#{scm.underscore}"
51 end
54 end
52
55
53 def user_setup
56 def user_setup
54 # Check the settings cache for each request
57 # Check the settings cache for each request
55 Setting.check_cache
58 Setting.check_cache
56 # Find the current user
59 # Find the current user
57 User.current = find_current_user
60 User.current = find_current_user
58 end
61 end
59
62
60 # Returns the current user or nil if no user is logged in
63 # Returns the current user or nil if no user is logged in
61 # and starts a session if needed
64 # and starts a session if needed
62 def find_current_user
65 def find_current_user
63 if session[:user_id]
66 if session[:user_id]
64 # existing session
67 # existing session
65 (User.active.find(session[:user_id]) rescue nil)
68 (User.active.find(session[:user_id]) rescue nil)
66 elsif cookies[:autologin] && Setting.autologin?
69 elsif cookies[:autologin] && Setting.autologin?
67 # auto-login feature starts a new session
70 # auto-login feature starts a new session
68 user = User.try_to_autologin(cookies[:autologin])
71 user = User.try_to_autologin(cookies[:autologin])
69 session[:user_id] = user.id if user
72 session[:user_id] = user.id if user
70 user
73 user
71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
74 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
72 # RSS key authentication does not start a session
75 # RSS key authentication does not start a session
73 User.find_by_rss_key(params[:key])
76 User.find_by_rss_key(params[:key])
74 elsif Setting.rest_api_enabled? && api_request?
77 elsif Setting.rest_api_enabled? && api_request?
75 if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action])
78 if (key = api_key_from_request) && accept_key_auth_actions.include?(params[:action])
76 # Use API key
79 # Use API key
77 User.find_by_api_key(key)
80 User.find_by_api_key(key)
78 else
81 else
79 # HTTP Basic, either username/password or API key/random
82 # HTTP Basic, either username/password or API key/random
80 authenticate_with_http_basic do |username, password|
83 authenticate_with_http_basic do |username, password|
81 User.try_to_login(username, password) || User.find_by_api_key(username)
84 User.try_to_login(username, password) || User.find_by_api_key(username)
82 end
85 end
83 end
86 end
84 end
87 end
85 end
88 end
86
89
87 # Sets the logged in user
90 # Sets the logged in user
88 def logged_user=(user)
91 def logged_user=(user)
89 reset_session
92 reset_session
90 if user && user.is_a?(User)
93 if user && user.is_a?(User)
91 User.current = user
94 User.current = user
92 session[:user_id] = user.id
95 session[:user_id] = user.id
93 else
96 else
94 User.current = User.anonymous
97 User.current = User.anonymous
95 end
98 end
96 end
99 end
97
100
98 # check if login is globally required to access the application
101 # check if login is globally required to access the application
99 def check_if_login_required
102 def check_if_login_required
100 # no check needed if user is already logged in
103 # no check needed if user is already logged in
101 return true if User.current.logged?
104 return true if User.current.logged?
102 require_login if Setting.login_required?
105 require_login if Setting.login_required?
103 end
106 end
104
107
105 def set_localization
108 def set_localization
106 lang = nil
109 lang = nil
107 if User.current.logged?
110 if User.current.logged?
108 lang = find_language(User.current.language)
111 lang = find_language(User.current.language)
109 end
112 end
110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
113 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
114 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
112 if !accept_lang.blank?
115 if !accept_lang.blank?
113 accept_lang = accept_lang.downcase
116 accept_lang = accept_lang.downcase
114 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
117 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
115 end
118 end
116 end
119 end
117 lang ||= Setting.default_language
120 lang ||= Setting.default_language
118 set_language_if_valid(lang)
121 set_language_if_valid(lang)
119 end
122 end
120
123
121 def require_login
124 def require_login
122 if !User.current.logged?
125 if !User.current.logged?
123 # Extract only the basic url parameters on non-GET requests
126 # Extract only the basic url parameters on non-GET requests
124 if request.get?
127 if request.get?
125 url = url_for(params)
128 url = url_for(params)
126 else
129 else
127 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
130 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
128 end
131 end
129 respond_to do |format|
132 respond_to do |format|
130 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
133 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
131 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
134 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
132 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
135 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
133 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
136 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
134 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
137 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
135 end
138 end
136 return false
139 return false
137 end
140 end
138 true
141 true
139 end
142 end
140
143
141 def require_admin
144 def require_admin
142 return unless require_login
145 return unless require_login
143 if !User.current.admin?
146 if !User.current.admin?
144 render_403
147 render_403
145 return false
148 return false
146 end
149 end
147 true
150 true
148 end
151 end
149
152
150 def deny_access
153 def deny_access
151 User.current.logged? ? render_403 : require_login
154 User.current.logged? ? render_403 : require_login
152 end
155 end
153
156
154 # Authorize the user for the requested action
157 # Authorize the user for the requested action
155 def authorize(ctrl = params[:controller], action = params[:action], global = false)
158 def authorize(ctrl = params[:controller], action = params[:action], global = false)
156 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
159 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
157 if allowed
160 if allowed
158 true
161 true
159 else
162 else
160 if @project && @project.archived?
163 if @project && @project.archived?
161 render_403 :message => :notice_not_authorized_archived_project
164 render_403 :message => :notice_not_authorized_archived_project
162 else
165 else
163 deny_access
166 deny_access
164 end
167 end
165 end
168 end
166 end
169 end
167
170
168 # Authorize the user for the requested action outside a project
171 # Authorize the user for the requested action outside a project
169 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
172 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
170 authorize(ctrl, action, global)
173 authorize(ctrl, action, global)
171 end
174 end
172
175
173 # Find project of id params[:id]
176 # Find project of id params[:id]
174 def find_project
177 def find_project
175 @project = Project.find(params[:id])
178 @project = Project.find(params[:id])
176 rescue ActiveRecord::RecordNotFound
179 rescue ActiveRecord::RecordNotFound
177 render_404
180 render_404
178 end
181 end
179
182
180 # Find project of id params[:project_id]
183 # Find project of id params[:project_id]
181 def find_project_by_project_id
184 def find_project_by_project_id
182 @project = Project.find(params[:project_id])
185 @project = Project.find(params[:project_id])
183 rescue ActiveRecord::RecordNotFound
186 rescue ActiveRecord::RecordNotFound
184 render_404
187 render_404
185 end
188 end
186
189
187 # Find a project based on params[:project_id]
190 # Find a project based on params[:project_id]
188 # TODO: some subclasses override this, see about merging their logic
191 # TODO: some subclasses override this, see about merging their logic
189 def find_optional_project
192 def find_optional_project
190 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
193 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
191 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
194 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
192 allowed ? true : deny_access
195 allowed ? true : deny_access
193 rescue ActiveRecord::RecordNotFound
196 rescue ActiveRecord::RecordNotFound
194 render_404
197 render_404
195 end
198 end
196
199
197 # Finds and sets @project based on @object.project
200 # Finds and sets @project based on @object.project
198 def find_project_from_association
201 def find_project_from_association
199 render_404 unless @object.present?
202 render_404 unless @object.present?
200
203
201 @project = @object.project
204 @project = @object.project
202 rescue ActiveRecord::RecordNotFound
205 rescue ActiveRecord::RecordNotFound
203 render_404
206 render_404
204 end
207 end
205
208
206 def find_model_object
209 def find_model_object
207 model = self.class.read_inheritable_attribute('model_object')
210 model = self.class.read_inheritable_attribute('model_object')
208 if model
211 if model
209 @object = model.find(params[:id])
212 @object = model.find(params[:id])
210 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
213 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
211 end
214 end
212 rescue ActiveRecord::RecordNotFound
215 rescue ActiveRecord::RecordNotFound
213 render_404
216 render_404
214 end
217 end
215
218
216 def self.model_object(model)
219 def self.model_object(model)
217 write_inheritable_attribute('model_object', model)
220 write_inheritable_attribute('model_object', model)
218 end
221 end
219
222
220 # Filter for bulk issue operations
223 # Filter for bulk issue operations
221 def find_issues
224 def find_issues
222 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
225 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
223 raise ActiveRecord::RecordNotFound if @issues.empty?
226 raise ActiveRecord::RecordNotFound if @issues.empty?
224 if @issues.detect {|issue| !issue.visible?}
227 if @issues.detect {|issue| !issue.visible?}
225 deny_access
228 deny_access
226 return
229 return
227 end
230 end
228 @projects = @issues.collect(&:project).compact.uniq
231 @projects = @issues.collect(&:project).compact.uniq
229 @project = @projects.first if @projects.size == 1
232 @project = @projects.first if @projects.size == 1
230 rescue ActiveRecord::RecordNotFound
233 rescue ActiveRecord::RecordNotFound
231 render_404
234 render_404
232 end
235 end
233
236
234 # Check if project is unique before bulk operations
237 # Check if project is unique before bulk operations
235 def check_project_uniqueness
238 def check_project_uniqueness
236 unless @project
239 unless @project
237 # TODO: let users bulk edit/move/destroy issues from different projects
240 # TODO: let users bulk edit/move/destroy issues from different projects
238 render_error 'Can not bulk edit/move/destroy issues from different projects'
241 render_error 'Can not bulk edit/move/destroy issues from different projects'
239 return false
242 return false
240 end
243 end
241 end
244 end
242
245
243 # make sure that the user is a member of the project (or admin) if project is private
246 # make sure that the user is a member of the project (or admin) if project is private
244 # used as a before_filter for actions that do not require any particular permission on the project
247 # used as a before_filter for actions that do not require any particular permission on the project
245 def check_project_privacy
248 def check_project_privacy
246 if @project && @project.active?
249 if @project && @project.active?
247 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
250 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
248 true
251 true
249 else
252 else
250 User.current.logged? ? render_403 : require_login
253 User.current.logged? ? render_403 : require_login
251 end
254 end
252 else
255 else
253 @project = nil
256 @project = nil
254 render_404
257 render_404
255 false
258 false
256 end
259 end
257 end
260 end
258
261
259 def back_url
262 def back_url
260 params[:back_url] || request.env['HTTP_REFERER']
263 params[:back_url] || request.env['HTTP_REFERER']
261 end
264 end
262
265
263 def redirect_back_or_default(default)
266 def redirect_back_or_default(default)
264 back_url = CGI.unescape(params[:back_url].to_s)
267 back_url = CGI.unescape(params[:back_url].to_s)
265 if !back_url.blank?
268 if !back_url.blank?
266 begin
269 begin
267 uri = URI.parse(back_url)
270 uri = URI.parse(back_url)
268 # do not redirect user to another host or to the login or register page
271 # do not redirect user to another host or to the login or register page
269 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
272 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
270 redirect_to(back_url)
273 redirect_to(back_url)
271 return
274 return
272 end
275 end
273 rescue URI::InvalidURIError
276 rescue URI::InvalidURIError
274 # redirect to default
277 # redirect to default
275 end
278 end
276 end
279 end
277 redirect_to default
280 redirect_to default
278 false
281 false
279 end
282 end
280
283
281 def render_403(options={})
284 def render_403(options={})
282 @project = nil
285 @project = nil
283 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
286 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
284 return false
287 return false
285 end
288 end
286
289
287 def render_404(options={})
290 def render_404(options={})
288 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
291 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
289 return false
292 return false
290 end
293 end
291
294
292 # Renders an error response
295 # Renders an error response
293 def render_error(arg)
296 def render_error(arg)
294 arg = {:message => arg} unless arg.is_a?(Hash)
297 arg = {:message => arg} unless arg.is_a?(Hash)
295
298
296 @message = arg[:message]
299 @message = arg[:message]
297 @message = l(@message) if @message.is_a?(Symbol)
300 @message = l(@message) if @message.is_a?(Symbol)
298 @status = arg[:status] || 500
301 @status = arg[:status] || 500
299
302
300 respond_to do |format|
303 respond_to do |format|
301 format.html {
304 format.html {
302 render :template => 'common/error', :layout => use_layout, :status => @status
305 render :template => 'common/error', :layout => use_layout, :status => @status
303 }
306 }
304 format.atom { head @status }
307 format.atom { head @status }
305 format.xml { head @status }
308 format.xml { head @status }
306 format.js { head @status }
309 format.js { head @status }
307 format.json { head @status }
310 format.json { head @status }
308 end
311 end
309 end
312 end
310
313
311 # Picks which layout to use based on the request
314 # Picks which layout to use based on the request
312 #
315 #
313 # @return [boolean, string] name of the layout to use or false for no layout
316 # @return [boolean, string] name of the layout to use or false for no layout
314 def use_layout
317 def use_layout
315 request.xhr? ? false : 'base'
318 request.xhr? ? false : 'base'
316 end
319 end
317
320
318 def invalid_authenticity_token
321 def invalid_authenticity_token
319 if api_request?
322 if api_request?
320 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
323 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
321 end
324 end
322 render_error "Invalid form authenticity token."
325 render_error "Invalid form authenticity token."
323 end
326 end
324
327
325 def render_feed(items, options={})
328 def render_feed(items, options={})
326 @items = items || []
329 @items = items || []
327 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
330 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
328 @items = @items.slice(0, Setting.feeds_limit.to_i)
331 @items = @items.slice(0, Setting.feeds_limit.to_i)
329 @title = options[:title] || Setting.app_title
332 @title = options[:title] || Setting.app_title
330 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
333 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
331 end
334 end
332
335
333 def self.accept_key_auth(*actions)
336 def self.accept_key_auth(*actions)
334 actions = actions.flatten.map(&:to_s)
337 actions = actions.flatten.map(&:to_s)
335 write_inheritable_attribute('accept_key_auth_actions', actions)
338 write_inheritable_attribute('accept_key_auth_actions', actions)
336 end
339 end
337
340
338 def accept_key_auth_actions
341 def accept_key_auth_actions
339 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
342 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
340 end
343 end
341
344
342 # Returns the number of objects that should be displayed
345 # Returns the number of objects that should be displayed
343 # on the paginated list
346 # on the paginated list
344 def per_page_option
347 def per_page_option
345 per_page = nil
348 per_page = nil
346 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
349 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
347 per_page = params[:per_page].to_s.to_i
350 per_page = params[:per_page].to_s.to_i
348 session[:per_page] = per_page
351 session[:per_page] = per_page
349 elsif session[:per_page]
352 elsif session[:per_page]
350 per_page = session[:per_page]
353 per_page = session[:per_page]
351 else
354 else
352 per_page = Setting.per_page_options_array.first || 25
355 per_page = Setting.per_page_options_array.first || 25
353 end
356 end
354 per_page
357 per_page
355 end
358 end
356
359
357 # Returns offset and limit used to retrieve objects
360 # Returns offset and limit used to retrieve objects
358 # for an API response based on offset, limit and page parameters
361 # for an API response based on offset, limit and page parameters
359 def api_offset_and_limit(options=params)
362 def api_offset_and_limit(options=params)
360 if options[:offset].present?
363 if options[:offset].present?
361 offset = options[:offset].to_i
364 offset = options[:offset].to_i
362 if offset < 0
365 if offset < 0
363 offset = 0
366 offset = 0
364 end
367 end
365 end
368 end
366 limit = options[:limit].to_i
369 limit = options[:limit].to_i
367 if limit < 1
370 if limit < 1
368 limit = 25
371 limit = 25
369 elsif limit > 100
372 elsif limit > 100
370 limit = 100
373 limit = 100
371 end
374 end
372 if offset.nil? && options[:page].present?
375 if offset.nil? && options[:page].present?
373 offset = (options[:page].to_i - 1) * limit
376 offset = (options[:page].to_i - 1) * limit
374 offset = 0 if offset < 0
377 offset = 0 if offset < 0
375 end
378 end
376 offset ||= 0
379 offset ||= 0
377
380
378 [offset, limit]
381 [offset, limit]
379 end
382 end
380
383
381 # qvalues http header parser
384 # qvalues http header parser
382 # code taken from webrick
385 # code taken from webrick
383 def parse_qvalues(value)
386 def parse_qvalues(value)
384 tmp = []
387 tmp = []
385 if value
388 if value
386 parts = value.split(/,\s*/)
389 parts = value.split(/,\s*/)
387 parts.each {|part|
390 parts.each {|part|
388 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
391 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
389 val = m[1]
392 val = m[1]
390 q = (m[2] or 1).to_f
393 q = (m[2] or 1).to_f
391 tmp.push([val, q])
394 tmp.push([val, q])
392 end
395 end
393 }
396 }
394 tmp = tmp.sort_by{|val, q| -q}
397 tmp = tmp.sort_by{|val, q| -q}
395 tmp.collect!{|val, q| val}
398 tmp.collect!{|val, q| val}
396 end
399 end
397 return tmp
400 return tmp
398 rescue
401 rescue
399 nil
402 nil
400 end
403 end
401
404
402 # Returns a string that can be used as filename value in Content-Disposition header
405 # Returns a string that can be used as filename value in Content-Disposition header
403 def filename_for_content_disposition(name)
406 def filename_for_content_disposition(name)
404 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
407 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
405 end
408 end
406
409
407 def api_request?
410 def api_request?
408 %w(xml json).include? params[:format]
411 %w(xml json).include? params[:format]
409 end
412 end
410
413
411 # Returns the API key present in the request
414 # Returns the API key present in the request
412 def api_key_from_request
415 def api_key_from_request
413 if params[:key].present?
416 if params[:key].present?
414 params[:key]
417 params[:key]
415 elsif request.headers["X-Redmine-API-Key"].present?
418 elsif request.headers["X-Redmine-API-Key"].present?
416 request.headers["X-Redmine-API-Key"]
419 request.headers["X-Redmine-API-Key"]
417 end
420 end
418 end
421 end
419
422
420 # Renders a warning flash if obj has unsaved attachments
423 # Renders a warning flash if obj has unsaved attachments
421 def render_attachment_warning_if_needed(obj)
424 def render_attachment_warning_if_needed(obj)
422 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
425 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
423 end
426 end
424
427
425 # Sets the `flash` notice or error based the number of issues that did not save
428 # Sets the `flash` notice or error based the number of issues that did not save
426 #
429 #
427 # @param [Array, Issue] issues all of the saved and unsaved Issues
430 # @param [Array, Issue] issues all of the saved and unsaved Issues
428 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
431 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
429 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
432 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
430 if unsaved_issue_ids.empty?
433 if unsaved_issue_ids.empty?
431 flash[:notice] = l(:notice_successful_update) unless issues.empty?
434 flash[:notice] = l(:notice_successful_update) unless issues.empty?
432 else
435 else
433 flash[:error] = l(:notice_failed_to_save_issues,
436 flash[:error] = l(:notice_failed_to_save_issues,
434 :count => unsaved_issue_ids.size,
437 :count => unsaved_issue_ids.size,
435 :total => issues.size,
438 :total => issues.size,
436 :ids => '#' + unsaved_issue_ids.join(', #'))
439 :ids => '#' + unsaved_issue_ids.join(', #'))
437 end
440 end
438 end
441 end
439
442
440 # Rescues an invalid query statement. Just in case...
443 # Rescues an invalid query statement. Just in case...
441 def query_statement_invalid(exception)
444 def query_statement_invalid(exception)
442 logger.error "Query::StatementInvalid: #{exception.message}" if logger
445 logger.error "Query::StatementInvalid: #{exception.message}" if logger
443 session.delete(:query)
446 session.delete(:query)
444 sort_clear if respond_to?(:sort_clear)
447 sort_clear if respond_to?(:sort_clear)
445 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
448 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
446 end
449 end
447
450
448 # Converts the errors on an ActiveRecord object into a common JSON format
451 # Converts the errors on an ActiveRecord object into a common JSON format
449 def object_errors_to_json(object)
452 def object_errors_to_json(object)
450 object.errors.collect do |attribute, error|
453 object.errors.collect do |attribute, error|
451 { attribute => error }
454 { attribute => error }
452 end.to_json
455 end.to_json
453 end
456 end
454
457
455 # Renders API response on validation failure
458 # Renders API response on validation failure
456 def render_validation_errors(object)
459 def render_validation_errors(object)
457 options = { :status => :unprocessable_entity, :layout => false }
460 options = { :status => :unprocessable_entity, :layout => false }
458 options.merge!(case params[:format]
461 options.merge!(case params[:format]
459 when 'xml'; { :xml => object.errors }
462 when 'xml'; { :xml => object.errors }
460 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
463 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
461 else
464 else
462 raise "Unknown format #{params[:format]} in #render_validation_errors"
465 raise "Unknown format #{params[:format]} in #render_validation_errors"
463 end
466 end
464 )
467 )
465 render options
468 render options
466 end
469 end
467
470
468 # Overrides #default_template so that the api template
471 # Overrides #default_template so that the api template
469 # is used automatically if it exists
472 # is used automatically if it exists
470 def default_template(action_name = self.action_name)
473 def default_template(action_name = self.action_name)
471 if api_request?
474 if api_request?
472 begin
475 begin
473 return self.view_paths.find_template(default_template_name(action_name), 'api')
476 return self.view_paths.find_template(default_template_name(action_name), 'api')
474 rescue ::ActionView::MissingTemplate
477 rescue ::ActionView::MissingTemplate
475 # the api template was not found
478 # the api template was not found
476 # fallback to the default behaviour
479 # fallback to the default behaviour
477 end
480 end
478 end
481 end
479 super
482 super
480 end
483 end
481
484
482 # Overrides #pick_layout so that #render with no arguments
485 # Overrides #pick_layout so that #render with no arguments
483 # doesn't use the layout for api requests
486 # doesn't use the layout for api requests
484 def pick_layout(*args)
487 def pick_layout(*args)
485 api_request? ? nil : super
488 api_request? ? nil : super
486 end
489 end
487 end
490 end
@@ -1,99 +1,100
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module QueriesHelper
18 module QueriesHelper
19
19
20 def operators_for_select(filter_type)
20 def operators_for_select(filter_type)
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
22 end
22 end
23
23
24 def column_header(column)
24 def column_header(column)
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
26 :default_order => column.default_order) :
26 :default_order => column.default_order) :
27 content_tag('th', column.caption)
27 content_tag('th', column.caption)
28 end
28 end
29
29
30 def column_content(column, issue)
30 def column_content(column, issue)
31 value = column.value(issue)
31 value = column.value(issue)
32
32
33 case value.class.name
33 case value.class.name
34 when 'String'
34 when 'String'
35 if column.name == :subject
35 if column.name == :subject
36 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
36 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
37 else
37 else
38 h(value)
38 h(value)
39 end
39 end
40 when 'Time'
40 when 'Time'
41 format_time(value)
41 format_time(value)
42 when 'Date'
42 when 'Date'
43 format_date(value)
43 format_date(value)
44 when 'Fixnum', 'Float'
44 when 'Fixnum', 'Float'
45 if column.name == :done_ratio
45 if column.name == :done_ratio
46 progress_bar(value, :width => '80px')
46 progress_bar(value, :width => '80px')
47 else
47 else
48 value.to_s
48 value.to_s
49 end
49 end
50 when 'User'
50 when 'User'
51 link_to_user value
51 link_to_user value
52 when 'Project'
52 when 'Project'
53 link_to_project value
53 link_to_project value
54 when 'Version'
54 when 'Version'
55 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
55 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
56 when 'TrueClass'
56 when 'TrueClass'
57 l(:general_text_Yes)
57 l(:general_text_Yes)
58 when 'FalseClass'
58 when 'FalseClass'
59 l(:general_text_No)
59 l(:general_text_No)
60 when 'Issue'
60 when 'Issue'
61 link_to_issue(value, :subject => false)
61 link_to_issue(value, :subject => false)
62 else
62 else
63 h(value)
63 h(value)
64 end
64 end
65 end
65 end
66
66
67 # Retrieve query from session or build a new query
67 # Retrieve query from session or build a new query
68 def retrieve_query
68 def retrieve_query
69 if !params[:query_id].blank?
69 if !params[:query_id].blank?
70 cond = "project_id IS NULL"
70 cond = "project_id IS NULL"
71 cond << " OR project_id = #{@project.id}" if @project
71 cond << " OR project_id = #{@project.id}" if @project
72 @query = Query.find(params[:query_id], :conditions => cond)
72 @query = Query.find(params[:query_id], :conditions => cond)
73 raise ::Unauthorized unless @query.visible?
73 @query.project = @project
74 @query.project = @project
74 session[:query] = {:id => @query.id, :project_id => @query.project_id}
75 session[:query] = {:id => @query.id, :project_id => @query.project_id}
75 sort_clear
76 sort_clear
76 else
77 else
77 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
78 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
78 # Give it a name, required to be valid
79 # Give it a name, required to be valid
79 @query = Query.new(:name => "_")
80 @query = Query.new(:name => "_")
80 @query.project = @project
81 @query.project = @project
81 if params[:fields] || params[:f]
82 if params[:fields] || params[:f]
82 @query.filters = {}
83 @query.filters = {}
83 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
84 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
84 else
85 else
85 @query.available_filters.keys.each do |field|
86 @query.available_filters.keys.each do |field|
86 @query.add_short_filter(field, params[field]) if params[field]
87 @query.add_short_filter(field, params[field]) if params[field]
87 end
88 end
88 end
89 end
89 @query.group_by = params[:group_by]
90 @query.group_by = params[:group_by]
90 @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
91 @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
91 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
92 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
92 else
93 else
93 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
94 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
94 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
95 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
95 @query.project = @project
96 @query.project = @project
96 end
97 end
97 end
98 end
98 end
99 end
99 end
100 end
@@ -1,673 +1,678
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !sortable.nil?
39 !sortable.nil?
40 end
40 end
41
41
42 def value(issue)
42 def value(issue)
43 issue.send name
43 issue.send name
44 end
44 end
45
45
46 def css_classes
46 def css_classes
47 name
47 name
48 end
48 end
49 end
49 end
50
50
51 class QueryCustomFieldColumn < QueryColumn
51 class QueryCustomFieldColumn < QueryColumn
52
52
53 def initialize(custom_field)
53 def initialize(custom_field)
54 self.name = "cf_#{custom_field.id}".to_sym
54 self.name = "cf_#{custom_field.id}".to_sym
55 self.sortable = custom_field.order_statement || false
55 self.sortable = custom_field.order_statement || false
56 if %w(list date bool int).include?(custom_field.field_format)
56 if %w(list date bool int).include?(custom_field.field_format)
57 self.groupable = custom_field.order_statement
57 self.groupable = custom_field.order_statement
58 end
58 end
59 self.groupable ||= false
59 self.groupable ||= false
60 @cf = custom_field
60 @cf = custom_field
61 end
61 end
62
62
63 def caption
63 def caption
64 @cf.name
64 @cf.name
65 end
65 end
66
66
67 def custom_field
67 def custom_field
68 @cf
68 @cf
69 end
69 end
70
70
71 def value(issue)
71 def value(issue)
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
73 cv && @cf.cast_value(cv.value)
73 cv && @cf.cast_value(cv.value)
74 end
74 end
75
75
76 def css_classes
76 def css_classes
77 @css_classes ||= "#{name} #{@cf.field_format}"
77 @css_classes ||= "#{name} #{@cf.field_format}"
78 end
78 end
79 end
79 end
80
80
81 class Query < ActiveRecord::Base
81 class Query < ActiveRecord::Base
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
83 end
83 end
84
84
85 belongs_to :project
85 belongs_to :project
86 belongs_to :user
86 belongs_to :user
87 serialize :filters
87 serialize :filters
88 serialize :column_names
88 serialize :column_names
89 serialize :sort_criteria, Array
89 serialize :sort_criteria, Array
90
90
91 attr_protected :project_id, :user_id
91 attr_protected :project_id, :user_id
92
92
93 validates_presence_of :name, :on => :save
93 validates_presence_of :name, :on => :save
94 validates_length_of :name, :maximum => 255
94 validates_length_of :name, :maximum => 255
95
95
96 @@operators = { "=" => :label_equals,
96 @@operators = { "=" => :label_equals,
97 "!" => :label_not_equals,
97 "!" => :label_not_equals,
98 "o" => :label_open_issues,
98 "o" => :label_open_issues,
99 "c" => :label_closed_issues,
99 "c" => :label_closed_issues,
100 "!*" => :label_none,
100 "!*" => :label_none,
101 "*" => :label_all,
101 "*" => :label_all,
102 ">=" => :label_greater_or_equal,
102 ">=" => :label_greater_or_equal,
103 "<=" => :label_less_or_equal,
103 "<=" => :label_less_or_equal,
104 "<t+" => :label_in_less_than,
104 "<t+" => :label_in_less_than,
105 ">t+" => :label_in_more_than,
105 ">t+" => :label_in_more_than,
106 "t+" => :label_in,
106 "t+" => :label_in,
107 "t" => :label_today,
107 "t" => :label_today,
108 "w" => :label_this_week,
108 "w" => :label_this_week,
109 ">t-" => :label_less_than_ago,
109 ">t-" => :label_less_than_ago,
110 "<t-" => :label_more_than_ago,
110 "<t-" => :label_more_than_ago,
111 "t-" => :label_ago,
111 "t-" => :label_ago,
112 "~" => :label_contains,
112 "~" => :label_contains,
113 "!~" => :label_not_contains }
113 "!~" => :label_not_contains }
114
114
115 cattr_reader :operators
115 cattr_reader :operators
116
116
117 @@operators_by_filter_type = { :list => [ "=", "!" ],
117 @@operators_by_filter_type = { :list => [ "=", "!" ],
118 :list_status => [ "o", "=", "!", "c", "*" ],
118 :list_status => [ "o", "=", "!", "c", "*" ],
119 :list_optional => [ "=", "!", "!*", "*" ],
119 :list_optional => [ "=", "!", "!*", "*" ],
120 :list_subprojects => [ "*", "!*", "=" ],
120 :list_subprojects => [ "*", "!*", "=" ],
121 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
121 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
122 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
122 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
123 :string => [ "=", "~", "!", "!~" ],
123 :string => [ "=", "~", "!", "!~" ],
124 :text => [ "~", "!~" ],
124 :text => [ "~", "!~" ],
125 :integer => [ "=", ">=", "<=", "!*", "*" ] }
125 :integer => [ "=", ">=", "<=", "!*", "*" ] }
126
126
127 cattr_reader :operators_by_filter_type
127 cattr_reader :operators_by_filter_type
128
128
129 @@available_columns = [
129 @@available_columns = [
130 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
130 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
131 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
131 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
132 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
132 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
133 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
133 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
134 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
134 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
135 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
135 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
136 QueryColumn.new(:author),
136 QueryColumn.new(:author),
137 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
137 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
138 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
138 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
139 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
139 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
140 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
140 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
141 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
141 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
142 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
142 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
143 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
143 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
144 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
144 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
145 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
145 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
146 ]
146 ]
147 cattr_reader :available_columns
147 cattr_reader :available_columns
148
148
149 def initialize(attributes = nil)
149 def initialize(attributes = nil)
150 super attributes
150 super attributes
151 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
151 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
152 end
152 end
153
153
154 def after_initialize
154 def after_initialize
155 # Store the fact that project is nil (used in #editable_by?)
155 # Store the fact that project is nil (used in #editable_by?)
156 @is_for_all = project.nil?
156 @is_for_all = project.nil?
157 end
157 end
158
158
159 def validate
159 def validate
160 filters.each_key do |field|
160 filters.each_key do |field|
161 errors.add label_for(field), :blank unless
161 errors.add label_for(field), :blank unless
162 # filter requires one or more values
162 # filter requires one or more values
163 (values_for(field) and !values_for(field).first.blank?) or
163 (values_for(field) and !values_for(field).first.blank?) or
164 # filter doesn't require any value
164 # filter doesn't require any value
165 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
165 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
166 end if filters
166 end if filters
167 end
167 end
168
169 # Returns true if the query is visible to +user+ or the current user.
170 def visible?(user=User.current)
171 self.is_public? || self.user_id == user.id
172 end
168
173
169 def editable_by?(user)
174 def editable_by?(user)
170 return false unless user
175 return false unless user
171 # Admin can edit them all and regular users can edit their private queries
176 # Admin can edit them all and regular users can edit their private queries
172 return true if user.admin? || (!is_public && self.user_id == user.id)
177 return true if user.admin? || (!is_public && self.user_id == user.id)
173 # Members can not edit public queries that are for all project (only admin is allowed to)
178 # Members can not edit public queries that are for all project (only admin is allowed to)
174 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
179 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
175 end
180 end
176
181
177 def available_filters
182 def available_filters
178 return @available_filters if @available_filters
183 return @available_filters if @available_filters
179
184
180 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
185 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
181
186
182 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
187 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
183 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
188 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
184 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
189 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
185 "subject" => { :type => :text, :order => 8 },
190 "subject" => { :type => :text, :order => 8 },
186 "created_on" => { :type => :date_past, :order => 9 },
191 "created_on" => { :type => :date_past, :order => 9 },
187 "updated_on" => { :type => :date_past, :order => 10 },
192 "updated_on" => { :type => :date_past, :order => 10 },
188 "start_date" => { :type => :date, :order => 11 },
193 "start_date" => { :type => :date, :order => 11 },
189 "due_date" => { :type => :date, :order => 12 },
194 "due_date" => { :type => :date, :order => 12 },
190 "estimated_hours" => { :type => :integer, :order => 13 },
195 "estimated_hours" => { :type => :integer, :order => 13 },
191 "done_ratio" => { :type => :integer, :order => 14 }}
196 "done_ratio" => { :type => :integer, :order => 14 }}
192
197
193 user_values = []
198 user_values = []
194 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
199 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
195 if project
200 if project
196 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
201 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
197 else
202 else
198 all_projects = Project.visible.all
203 all_projects = Project.visible.all
199 if all_projects.any?
204 if all_projects.any?
200 # members of visible projects
205 # members of visible projects
201 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
206 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] }
202
207
203 # project filter
208 # project filter
204 project_values = []
209 project_values = []
205 Project.project_tree(all_projects) do |p, level|
210 Project.project_tree(all_projects) do |p, level|
206 prefix = (level > 0 ? ('--' * level + ' ') : '')
211 prefix = (level > 0 ? ('--' * level + ' ') : '')
207 project_values << ["#{prefix}#{p.name}", p.id.to_s]
212 project_values << ["#{prefix}#{p.name}", p.id.to_s]
208 end
213 end
209 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
214 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
210 end
215 end
211 end
216 end
212 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
217 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
213 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
218 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
214
219
215 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
220 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
216 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
221 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
217
222
218 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
223 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
219 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
224 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
220
225
221 if User.current.logged?
226 if User.current.logged?
222 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
227 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
223 end
228 end
224
229
225 if project
230 if project
226 # project specific filters
231 # project specific filters
227 categories = @project.issue_categories.all
232 categories = @project.issue_categories.all
228 unless categories.empty?
233 unless categories.empty?
229 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
234 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
230 end
235 end
231 versions = @project.shared_versions.all
236 versions = @project.shared_versions.all
232 unless versions.empty?
237 unless versions.empty?
233 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
238 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
234 end
239 end
235 unless @project.leaf?
240 unless @project.leaf?
236 subprojects = @project.descendants.visible.all
241 subprojects = @project.descendants.visible.all
237 unless subprojects.empty?
242 unless subprojects.empty?
238 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
243 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
239 end
244 end
240 end
245 end
241 add_custom_fields_filters(@project.all_issue_custom_fields)
246 add_custom_fields_filters(@project.all_issue_custom_fields)
242 else
247 else
243 # global filters for cross project issue list
248 # global filters for cross project issue list
244 system_shared_versions = Version.visible.find_all_by_sharing('system')
249 system_shared_versions = Version.visible.find_all_by_sharing('system')
245 unless system_shared_versions.empty?
250 unless system_shared_versions.empty?
246 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
251 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
247 end
252 end
248 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
253 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
249 end
254 end
250 @available_filters
255 @available_filters
251 end
256 end
252
257
253 def add_filter(field, operator, values)
258 def add_filter(field, operator, values)
254 # values must be an array
259 # values must be an array
255 return unless values and values.is_a? Array # and !values.first.empty?
260 return unless values and values.is_a? Array # and !values.first.empty?
256 # check if field is defined as an available filter
261 # check if field is defined as an available filter
257 if available_filters.has_key? field
262 if available_filters.has_key? field
258 filter_options = available_filters[field]
263 filter_options = available_filters[field]
259 # check if operator is allowed for that filter
264 # check if operator is allowed for that filter
260 #if @@operators_by_filter_type[filter_options[:type]].include? operator
265 #if @@operators_by_filter_type[filter_options[:type]].include? operator
261 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
266 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
262 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
267 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
263 #end
268 #end
264 filters[field] = {:operator => operator, :values => values }
269 filters[field] = {:operator => operator, :values => values }
265 end
270 end
266 end
271 end
267
272
268 def add_short_filter(field, expression)
273 def add_short_filter(field, expression)
269 return unless expression
274 return unless expression
270 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
275 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
271 add_filter field, (parms[0] || "="), [parms[1] || ""]
276 add_filter field, (parms[0] || "="), [parms[1] || ""]
272 end
277 end
273
278
274 # Add multiple filters using +add_filter+
279 # Add multiple filters using +add_filter+
275 def add_filters(fields, operators, values)
280 def add_filters(fields, operators, values)
276 if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
281 if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
277 fields.each do |field|
282 fields.each do |field|
278 add_filter(field, operators[field], values[field])
283 add_filter(field, operators[field], values[field])
279 end
284 end
280 end
285 end
281 end
286 end
282
287
283 def has_filter?(field)
288 def has_filter?(field)
284 filters and filters[field]
289 filters and filters[field]
285 end
290 end
286
291
287 def operator_for(field)
292 def operator_for(field)
288 has_filter?(field) ? filters[field][:operator] : nil
293 has_filter?(field) ? filters[field][:operator] : nil
289 end
294 end
290
295
291 def values_for(field)
296 def values_for(field)
292 has_filter?(field) ? filters[field][:values] : nil
297 has_filter?(field) ? filters[field][:values] : nil
293 end
298 end
294
299
295 def label_for(field)
300 def label_for(field)
296 label = available_filters[field][:name] if available_filters.has_key?(field)
301 label = available_filters[field][:name] if available_filters.has_key?(field)
297 label ||= field.gsub(/\_id$/, "")
302 label ||= field.gsub(/\_id$/, "")
298 end
303 end
299
304
300 def available_columns
305 def available_columns
301 return @available_columns if @available_columns
306 return @available_columns if @available_columns
302 @available_columns = Query.available_columns
307 @available_columns = Query.available_columns
303 @available_columns += (project ?
308 @available_columns += (project ?
304 project.all_issue_custom_fields :
309 project.all_issue_custom_fields :
305 IssueCustomField.find(:all)
310 IssueCustomField.find(:all)
306 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
311 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
307 end
312 end
308
313
309 def self.available_columns=(v)
314 def self.available_columns=(v)
310 self.available_columns = (v)
315 self.available_columns = (v)
311 end
316 end
312
317
313 def self.add_available_column(column)
318 def self.add_available_column(column)
314 self.available_columns << (column) if column.is_a?(QueryColumn)
319 self.available_columns << (column) if column.is_a?(QueryColumn)
315 end
320 end
316
321
317 # Returns an array of columns that can be used to group the results
322 # Returns an array of columns that can be used to group the results
318 def groupable_columns
323 def groupable_columns
319 available_columns.select {|c| c.groupable}
324 available_columns.select {|c| c.groupable}
320 end
325 end
321
326
322 # Returns a Hash of columns and the key for sorting
327 # Returns a Hash of columns and the key for sorting
323 def sortable_columns
328 def sortable_columns
324 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
329 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
325 h[column.name.to_s] = column.sortable
330 h[column.name.to_s] = column.sortable
326 h
331 h
327 })
332 })
328 end
333 end
329
334
330 def columns
335 def columns
331 if has_default_columns?
336 if has_default_columns?
332 available_columns.select do |c|
337 available_columns.select do |c|
333 # Adds the project column by default for cross-project lists
338 # Adds the project column by default for cross-project lists
334 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
339 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
335 end
340 end
336 else
341 else
337 # preserve the column_names order
342 # preserve the column_names order
338 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
343 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
339 end
344 end
340 end
345 end
341
346
342 def column_names=(names)
347 def column_names=(names)
343 if names
348 if names
344 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
349 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
345 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
350 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
346 # Set column_names to nil if default columns
351 # Set column_names to nil if default columns
347 if names.map(&:to_s) == Setting.issue_list_default_columns
352 if names.map(&:to_s) == Setting.issue_list_default_columns
348 names = nil
353 names = nil
349 end
354 end
350 end
355 end
351 write_attribute(:column_names, names)
356 write_attribute(:column_names, names)
352 end
357 end
353
358
354 def has_column?(column)
359 def has_column?(column)
355 column_names && column_names.include?(column.name)
360 column_names && column_names.include?(column.name)
356 end
361 end
357
362
358 def has_default_columns?
363 def has_default_columns?
359 column_names.nil? || column_names.empty?
364 column_names.nil? || column_names.empty?
360 end
365 end
361
366
362 def sort_criteria=(arg)
367 def sort_criteria=(arg)
363 c = []
368 c = []
364 if arg.is_a?(Hash)
369 if arg.is_a?(Hash)
365 arg = arg.keys.sort.collect {|k| arg[k]}
370 arg = arg.keys.sort.collect {|k| arg[k]}
366 end
371 end
367 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
372 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
368 write_attribute(:sort_criteria, c)
373 write_attribute(:sort_criteria, c)
369 end
374 end
370
375
371 def sort_criteria
376 def sort_criteria
372 read_attribute(:sort_criteria) || []
377 read_attribute(:sort_criteria) || []
373 end
378 end
374
379
375 def sort_criteria_key(arg)
380 def sort_criteria_key(arg)
376 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
381 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
377 end
382 end
378
383
379 def sort_criteria_order(arg)
384 def sort_criteria_order(arg)
380 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
385 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
381 end
386 end
382
387
383 # Returns the SQL sort order that should be prepended for grouping
388 # Returns the SQL sort order that should be prepended for grouping
384 def group_by_sort_order
389 def group_by_sort_order
385 if grouped? && (column = group_by_column)
390 if grouped? && (column = group_by_column)
386 column.sortable.is_a?(Array) ?
391 column.sortable.is_a?(Array) ?
387 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
392 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
388 "#{column.sortable} #{column.default_order}"
393 "#{column.sortable} #{column.default_order}"
389 end
394 end
390 end
395 end
391
396
392 # Returns true if the query is a grouped query
397 # Returns true if the query is a grouped query
393 def grouped?
398 def grouped?
394 !group_by_column.nil?
399 !group_by_column.nil?
395 end
400 end
396
401
397 def group_by_column
402 def group_by_column
398 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
403 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
399 end
404 end
400
405
401 def group_by_statement
406 def group_by_statement
402 group_by_column.try(:groupable)
407 group_by_column.try(:groupable)
403 end
408 end
404
409
405 def project_statement
410 def project_statement
406 project_clauses = []
411 project_clauses = []
407 if project && !@project.descendants.active.empty?
412 if project && !@project.descendants.active.empty?
408 ids = [project.id]
413 ids = [project.id]
409 if has_filter?("subproject_id")
414 if has_filter?("subproject_id")
410 case operator_for("subproject_id")
415 case operator_for("subproject_id")
411 when '='
416 when '='
412 # include the selected subprojects
417 # include the selected subprojects
413 ids += values_for("subproject_id").each(&:to_i)
418 ids += values_for("subproject_id").each(&:to_i)
414 when '!*'
419 when '!*'
415 # main project only
420 # main project only
416 else
421 else
417 # all subprojects
422 # all subprojects
418 ids += project.descendants.collect(&:id)
423 ids += project.descendants.collect(&:id)
419 end
424 end
420 elsif Setting.display_subprojects_issues?
425 elsif Setting.display_subprojects_issues?
421 ids += project.descendants.collect(&:id)
426 ids += project.descendants.collect(&:id)
422 end
427 end
423 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
428 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
424 elsif project
429 elsif project
425 project_clauses << "#{Project.table_name}.id = %d" % project.id
430 project_clauses << "#{Project.table_name}.id = %d" % project.id
426 end
431 end
427 project_clauses.any? ? project_clauses.join(' AND ') : nil
432 project_clauses.any? ? project_clauses.join(' AND ') : nil
428 end
433 end
429
434
430 def statement
435 def statement
431 # filters clauses
436 # filters clauses
432 filters_clauses = []
437 filters_clauses = []
433 filters.each_key do |field|
438 filters.each_key do |field|
434 next if field == "subproject_id"
439 next if field == "subproject_id"
435 v = values_for(field).clone
440 v = values_for(field).clone
436 next unless v and !v.empty?
441 next unless v and !v.empty?
437 operator = operator_for(field)
442 operator = operator_for(field)
438
443
439 # "me" value subsitution
444 # "me" value subsitution
440 if %w(assigned_to_id author_id watcher_id).include?(field)
445 if %w(assigned_to_id author_id watcher_id).include?(field)
441 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
446 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
442 end
447 end
443
448
444 sql = ''
449 sql = ''
445 if field =~ /^cf_(\d+)$/
450 if field =~ /^cf_(\d+)$/
446 # custom field
451 # custom field
447 db_table = CustomValue.table_name
452 db_table = CustomValue.table_name
448 db_field = 'value'
453 db_field = 'value'
449 is_custom_filter = true
454 is_custom_filter = true
450 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
455 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
451 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
456 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
452 elsif field == 'watcher_id'
457 elsif field == 'watcher_id'
453 db_table = Watcher.table_name
458 db_table = Watcher.table_name
454 db_field = 'user_id'
459 db_field = 'user_id'
455 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
460 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
456 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
461 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
457 elsif field == "member_of_group" # named field
462 elsif field == "member_of_group" # named field
458 if operator == '*' # Any group
463 if operator == '*' # Any group
459 groups = Group.all
464 groups = Group.all
460 operator = '=' # Override the operator since we want to find by assigned_to
465 operator = '=' # Override the operator since we want to find by assigned_to
461 elsif operator == "!*"
466 elsif operator == "!*"
462 groups = Group.all
467 groups = Group.all
463 operator = '!' # Override the operator since we want to find by assigned_to
468 operator = '!' # Override the operator since we want to find by assigned_to
464 else
469 else
465 groups = Group.find_all_by_id(v)
470 groups = Group.find_all_by_id(v)
466 end
471 end
467 groups ||= []
472 groups ||= []
468
473
469 members_of_groups = groups.inject([]) {|user_ids, group|
474 members_of_groups = groups.inject([]) {|user_ids, group|
470 if group && group.user_ids.present?
475 if group && group.user_ids.present?
471 user_ids << group.user_ids
476 user_ids << group.user_ids
472 end
477 end
473 user_ids.flatten.uniq.compact
478 user_ids.flatten.uniq.compact
474 }.sort.collect(&:to_s)
479 }.sort.collect(&:to_s)
475
480
476 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
481 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
477
482
478 elsif field == "assigned_to_role" # named field
483 elsif field == "assigned_to_role" # named field
479 if operator == "*" # Any Role
484 if operator == "*" # Any Role
480 roles = Role.givable
485 roles = Role.givable
481 operator = '=' # Override the operator since we want to find by assigned_to
486 operator = '=' # Override the operator since we want to find by assigned_to
482 elsif operator == "!*" # No role
487 elsif operator == "!*" # No role
483 roles = Role.givable
488 roles = Role.givable
484 operator = '!' # Override the operator since we want to find by assigned_to
489 operator = '!' # Override the operator since we want to find by assigned_to
485 else
490 else
486 roles = Role.givable.find_all_by_id(v)
491 roles = Role.givable.find_all_by_id(v)
487 end
492 end
488 roles ||= []
493 roles ||= []
489
494
490 members_of_roles = roles.inject([]) {|user_ids, role|
495 members_of_roles = roles.inject([]) {|user_ids, role|
491 if role && role.members
496 if role && role.members
492 user_ids << role.members.collect(&:user_id)
497 user_ids << role.members.collect(&:user_id)
493 end
498 end
494 user_ids.flatten.uniq.compact
499 user_ids.flatten.uniq.compact
495 }.sort.collect(&:to_s)
500 }.sort.collect(&:to_s)
496
501
497 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
502 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
498 else
503 else
499 # regular field
504 # regular field
500 db_table = Issue.table_name
505 db_table = Issue.table_name
501 db_field = field
506 db_field = field
502 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
507 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
503 end
508 end
504 filters_clauses << sql
509 filters_clauses << sql
505
510
506 end if filters and valid?
511 end if filters and valid?
507
512
508 filters_clauses << project_statement
513 filters_clauses << project_statement
509 filters_clauses.reject!(&:blank?)
514 filters_clauses.reject!(&:blank?)
510
515
511 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
516 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
512 end
517 end
513
518
514 # Returns the issue count
519 # Returns the issue count
515 def issue_count
520 def issue_count
516 Issue.count(:include => [:status, :project], :conditions => statement)
521 Issue.count(:include => [:status, :project], :conditions => statement)
517 rescue ::ActiveRecord::StatementInvalid => e
522 rescue ::ActiveRecord::StatementInvalid => e
518 raise StatementInvalid.new(e.message)
523 raise StatementInvalid.new(e.message)
519 end
524 end
520
525
521 # Returns the issue count by group or nil if query is not grouped
526 # Returns the issue count by group or nil if query is not grouped
522 def issue_count_by_group
527 def issue_count_by_group
523 r = nil
528 r = nil
524 if grouped?
529 if grouped?
525 begin
530 begin
526 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
531 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
527 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
532 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
528 rescue ActiveRecord::RecordNotFound
533 rescue ActiveRecord::RecordNotFound
529 r = {nil => issue_count}
534 r = {nil => issue_count}
530 end
535 end
531 c = group_by_column
536 c = group_by_column
532 if c.is_a?(QueryCustomFieldColumn)
537 if c.is_a?(QueryCustomFieldColumn)
533 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
538 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
534 end
539 end
535 end
540 end
536 r
541 r
537 rescue ::ActiveRecord::StatementInvalid => e
542 rescue ::ActiveRecord::StatementInvalid => e
538 raise StatementInvalid.new(e.message)
543 raise StatementInvalid.new(e.message)
539 end
544 end
540
545
541 # Returns the issues
546 # Returns the issues
542 # Valid options are :order, :offset, :limit, :include, :conditions
547 # Valid options are :order, :offset, :limit, :include, :conditions
543 def issues(options={})
548 def issues(options={})
544 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
549 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
545 order_option = nil if order_option.blank?
550 order_option = nil if order_option.blank?
546
551
547 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
552 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
548 :conditions => Query.merge_conditions(statement, options[:conditions]),
553 :conditions => Query.merge_conditions(statement, options[:conditions]),
549 :order => order_option,
554 :order => order_option,
550 :limit => options[:limit],
555 :limit => options[:limit],
551 :offset => options[:offset]
556 :offset => options[:offset]
552 rescue ::ActiveRecord::StatementInvalid => e
557 rescue ::ActiveRecord::StatementInvalid => e
553 raise StatementInvalid.new(e.message)
558 raise StatementInvalid.new(e.message)
554 end
559 end
555
560
556 # Returns the journals
561 # Returns the journals
557 # Valid options are :order, :offset, :limit
562 # Valid options are :order, :offset, :limit
558 def journals(options={})
563 def journals(options={})
559 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
564 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
560 :conditions => statement,
565 :conditions => statement,
561 :order => options[:order],
566 :order => options[:order],
562 :limit => options[:limit],
567 :limit => options[:limit],
563 :offset => options[:offset]
568 :offset => options[:offset]
564 rescue ::ActiveRecord::StatementInvalid => e
569 rescue ::ActiveRecord::StatementInvalid => e
565 raise StatementInvalid.new(e.message)
570 raise StatementInvalid.new(e.message)
566 end
571 end
567
572
568 # Returns the versions
573 # Returns the versions
569 # Valid options are :conditions
574 # Valid options are :conditions
570 def versions(options={})
575 def versions(options={})
571 Version.visible.find :all, :include => :project,
576 Version.visible.find :all, :include => :project,
572 :conditions => Query.merge_conditions(project_statement, options[:conditions])
577 :conditions => Query.merge_conditions(project_statement, options[:conditions])
573 rescue ::ActiveRecord::StatementInvalid => e
578 rescue ::ActiveRecord::StatementInvalid => e
574 raise StatementInvalid.new(e.message)
579 raise StatementInvalid.new(e.message)
575 end
580 end
576
581
577 private
582 private
578
583
579 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
584 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
580 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
585 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
581 sql = ''
586 sql = ''
582 case operator
587 case operator
583 when "="
588 when "="
584 if value.any?
589 if value.any?
585 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
590 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
586 else
591 else
587 # IN an empty set
592 # IN an empty set
588 sql = "1=0"
593 sql = "1=0"
589 end
594 end
590 when "!"
595 when "!"
591 if value.any?
596 if value.any?
592 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
597 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
593 else
598 else
594 # NOT IN an empty set
599 # NOT IN an empty set
595 sql = "1=1"
600 sql = "1=1"
596 end
601 end
597 when "!*"
602 when "!*"
598 sql = "#{db_table}.#{db_field} IS NULL"
603 sql = "#{db_table}.#{db_field} IS NULL"
599 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
604 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
600 when "*"
605 when "*"
601 sql = "#{db_table}.#{db_field} IS NOT NULL"
606 sql = "#{db_table}.#{db_field} IS NOT NULL"
602 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
607 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
603 when ">="
608 when ">="
604 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
609 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
605 when "<="
610 when "<="
606 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
611 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
607 when "o"
612 when "o"
608 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
613 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
609 when "c"
614 when "c"
610 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
615 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
611 when ">t-"
616 when ">t-"
612 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
617 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
613 when "<t-"
618 when "<t-"
614 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
619 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
615 when "t-"
620 when "t-"
616 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
621 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
617 when ">t+"
622 when ">t+"
618 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
623 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
619 when "<t+"
624 when "<t+"
620 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
625 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
621 when "t+"
626 when "t+"
622 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
627 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
623 when "t"
628 when "t"
624 sql = date_range_clause(db_table, db_field, 0, 0)
629 sql = date_range_clause(db_table, db_field, 0, 0)
625 when "w"
630 when "w"
626 first_day_of_week = l(:general_first_day_of_week).to_i
631 first_day_of_week = l(:general_first_day_of_week).to_i
627 day_of_week = Date.today.cwday
632 day_of_week = Date.today.cwday
628 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
633 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
629 sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6)
634 sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6)
630 when "~"
635 when "~"
631 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
636 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
632 when "!~"
637 when "!~"
633 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
638 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
634 end
639 end
635
640
636 return sql
641 return sql
637 end
642 end
638
643
639 def add_custom_fields_filters(custom_fields)
644 def add_custom_fields_filters(custom_fields)
640 @available_filters ||= {}
645 @available_filters ||= {}
641
646
642 custom_fields.select(&:is_filter?).each do |field|
647 custom_fields.select(&:is_filter?).each do |field|
643 case field.field_format
648 case field.field_format
644 when "text"
649 when "text"
645 options = { :type => :text, :order => 20 }
650 options = { :type => :text, :order => 20 }
646 when "list"
651 when "list"
647 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
652 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
648 when "date"
653 when "date"
649 options = { :type => :date, :order => 20 }
654 options = { :type => :date, :order => 20 }
650 when "bool"
655 when "bool"
651 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
656 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
652 when "user", "version"
657 when "user", "version"
653 next unless project
658 next unless project
654 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
659 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
655 else
660 else
656 options = { :type => :string, :order => 20 }
661 options = { :type => :string, :order => 20 }
657 end
662 end
658 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
663 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
659 end
664 end
660 end
665 end
661
666
662 # Returns a SQL clause for a date or datetime field.
667 # Returns a SQL clause for a date or datetime field.
663 def date_range_clause(table, field, from, to)
668 def date_range_clause(table, field, from, to)
664 s = []
669 s = []
665 if from
670 if from
666 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
671 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
667 end
672 end
668 if to
673 if to
669 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
674 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
670 end
675 end
671 s.join(' AND ')
676 s.join(' AND ')
672 end
677 end
673 end
678 end
@@ -1,1490 +1,1511
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
23
24 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
25 fixtures :projects,
22 fixtures :projects,
26 :users,
23 :users,
27 :roles,
24 :roles,
28 :members,
25 :members,
29 :member_roles,
26 :member_roles,
30 :issues,
27 :issues,
31 :issue_statuses,
28 :issue_statuses,
32 :versions,
29 :versions,
33 :trackers,
30 :trackers,
34 :projects_trackers,
31 :projects_trackers,
35 :issue_categories,
32 :issue_categories,
36 :enabled_modules,
33 :enabled_modules,
37 :enumerations,
34 :enumerations,
38 :attachments,
35 :attachments,
39 :workflows,
36 :workflows,
40 :custom_fields,
37 :custom_fields,
41 :custom_values,
38 :custom_values,
42 :custom_fields_projects,
39 :custom_fields_projects,
43 :custom_fields_trackers,
40 :custom_fields_trackers,
44 :time_entries,
41 :time_entries,
45 :journals,
42 :journals,
46 :journal_details,
43 :journal_details,
47 :queries
44 :queries
48
45
49 def setup
46 def setup
50 @controller = IssuesController.new
47 @controller = IssuesController.new
51 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
52 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
53 User.current = nil
50 User.current = nil
54 end
51 end
55
52
56 def test_index
53 def test_index
57 Setting.default_language = 'en'
54 Setting.default_language = 'en'
58
55
59 get :index
56 get :index
60 assert_response :success
57 assert_response :success
61 assert_template 'index.rhtml'
58 assert_template 'index.rhtml'
62 assert_not_nil assigns(:issues)
59 assert_not_nil assigns(:issues)
63 assert_nil assigns(:project)
60 assert_nil assigns(:project)
64 assert_tag :tag => 'a', :content => /Can't print recipes/
61 assert_tag :tag => 'a', :content => /Can't print recipes/
65 assert_tag :tag => 'a', :content => /Subproject issue/
62 assert_tag :tag => 'a', :content => /Subproject issue/
66 # private projects hidden
63 # private projects hidden
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
64 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
65 assert_no_tag :tag => 'a', :content => /Issue on project 2/
69 # project column
66 # project column
70 assert_tag :tag => 'th', :content => /Project/
67 assert_tag :tag => 'th', :content => /Project/
71 end
68 end
72
69
73 def test_index_should_not_list_issues_when_module_disabled
70 def test_index_should_not_list_issues_when_module_disabled
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
71 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 get :index
72 get :index
76 assert_response :success
73 assert_response :success
77 assert_template 'index.rhtml'
74 assert_template 'index.rhtml'
78 assert_not_nil assigns(:issues)
75 assert_not_nil assigns(:issues)
79 assert_nil assigns(:project)
76 assert_nil assigns(:project)
80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
77 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 assert_tag :tag => 'a', :content => /Subproject issue/
78 assert_tag :tag => 'a', :content => /Subproject issue/
82 end
79 end
83
80
84 def test_index_should_not_list_issues_when_module_disabled
81 def test_index_should_not_list_issues_when_module_disabled
85 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
82 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
86 get :index
83 get :index
87 assert_response :success
84 assert_response :success
88 assert_template 'index.rhtml'
85 assert_template 'index.rhtml'
89 assert_not_nil assigns(:issues)
86 assert_not_nil assigns(:issues)
90 assert_nil assigns(:project)
87 assert_nil assigns(:project)
91 assert_no_tag :tag => 'a', :content => /Can't print recipes/
88 assert_no_tag :tag => 'a', :content => /Can't print recipes/
92 assert_tag :tag => 'a', :content => /Subproject issue/
89 assert_tag :tag => 'a', :content => /Subproject issue/
93 end
90 end
94
91
95 def test_index_should_list_visible_issues_only
92 def test_index_should_list_visible_issues_only
96 get :index, :per_page => 100
93 get :index, :per_page => 100
97 assert_response :success
94 assert_response :success
98 assert_not_nil assigns(:issues)
95 assert_not_nil assigns(:issues)
99 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
96 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
100 end
97 end
101
98
102 def test_index_with_project
99 def test_index_with_project
103 Setting.display_subprojects_issues = 0
100 Setting.display_subprojects_issues = 0
104 get :index, :project_id => 1
101 get :index, :project_id => 1
105 assert_response :success
102 assert_response :success
106 assert_template 'index.rhtml'
103 assert_template 'index.rhtml'
107 assert_not_nil assigns(:issues)
104 assert_not_nil assigns(:issues)
108 assert_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Can't print recipes/
109 assert_no_tag :tag => 'a', :content => /Subproject issue/
106 assert_no_tag :tag => 'a', :content => /Subproject issue/
110 end
107 end
111
108
112 def test_index_with_project_and_subprojects
109 def test_index_with_project_and_subprojects
113 Setting.display_subprojects_issues = 1
110 Setting.display_subprojects_issues = 1
114 get :index, :project_id => 1
111 get :index, :project_id => 1
115 assert_response :success
112 assert_response :success
116 assert_template 'index.rhtml'
113 assert_template 'index.rhtml'
117 assert_not_nil assigns(:issues)
114 assert_not_nil assigns(:issues)
118 assert_tag :tag => 'a', :content => /Can't print recipes/
115 assert_tag :tag => 'a', :content => /Can't print recipes/
119 assert_tag :tag => 'a', :content => /Subproject issue/
116 assert_tag :tag => 'a', :content => /Subproject issue/
120 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
117 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
121 end
118 end
122
119
123 def test_index_with_project_and_subprojects_should_show_private_subprojects
120 def test_index_with_project_and_subprojects_should_show_private_subprojects
124 @request.session[:user_id] = 2
121 @request.session[:user_id] = 2
125 Setting.display_subprojects_issues = 1
122 Setting.display_subprojects_issues = 1
126 get :index, :project_id => 1
123 get :index, :project_id => 1
127 assert_response :success
124 assert_response :success
128 assert_template 'index.rhtml'
125 assert_template 'index.rhtml'
129 assert_not_nil assigns(:issues)
126 assert_not_nil assigns(:issues)
130 assert_tag :tag => 'a', :content => /Can't print recipes/
127 assert_tag :tag => 'a', :content => /Can't print recipes/
131 assert_tag :tag => 'a', :content => /Subproject issue/
128 assert_tag :tag => 'a', :content => /Subproject issue/
132 assert_tag :tag => 'a', :content => /Issue of a private subproject/
129 assert_tag :tag => 'a', :content => /Issue of a private subproject/
133 end
130 end
134
131
135 def test_index_with_project_and_default_filter
132 def test_index_with_project_and_default_filter
136 get :index, :project_id => 1, :set_filter => 1
133 get :index, :project_id => 1, :set_filter => 1
137 assert_response :success
134 assert_response :success
138 assert_template 'index.rhtml'
135 assert_template 'index.rhtml'
139 assert_not_nil assigns(:issues)
136 assert_not_nil assigns(:issues)
140
137
141 query = assigns(:query)
138 query = assigns(:query)
142 assert_not_nil query
139 assert_not_nil query
143 # default filter
140 # default filter
144 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
141 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
145 end
142 end
146
143
147 def test_index_with_project_and_filter
144 def test_index_with_project_and_filter
148 get :index, :project_id => 1, :set_filter => 1,
145 get :index, :project_id => 1, :set_filter => 1,
149 :f => ['tracker_id'],
146 :f => ['tracker_id'],
150 :op => {'tracker_id' => '='},
147 :op => {'tracker_id' => '='},
151 :v => {'tracker_id' => ['1']}
148 :v => {'tracker_id' => ['1']}
152 assert_response :success
149 assert_response :success
153 assert_template 'index.rhtml'
150 assert_template 'index.rhtml'
154 assert_not_nil assigns(:issues)
151 assert_not_nil assigns(:issues)
155
152
156 query = assigns(:query)
153 query = assigns(:query)
157 assert_not_nil query
154 assert_not_nil query
158 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
155 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
159 end
156 end
160
157
161 def test_index_with_project_and_empty_filters
158 def test_index_with_project_and_empty_filters
162 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
159 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
163 assert_response :success
160 assert_response :success
164 assert_template 'index.rhtml'
161 assert_template 'index.rhtml'
165 assert_not_nil assigns(:issues)
162 assert_not_nil assigns(:issues)
166
163
167 query = assigns(:query)
164 query = assigns(:query)
168 assert_not_nil query
165 assert_not_nil query
169 # no filter
166 # no filter
170 assert_equal({}, query.filters)
167 assert_equal({}, query.filters)
171 end
168 end
172
169
173 def test_index_with_query
170 def test_index_with_query
174 get :index, :project_id => 1, :query_id => 5
171 get :index, :project_id => 1, :query_id => 5
175 assert_response :success
172 assert_response :success
176 assert_template 'index.rhtml'
173 assert_template 'index.rhtml'
177 assert_not_nil assigns(:issues)
174 assert_not_nil assigns(:issues)
178 assert_nil assigns(:issue_count_by_group)
175 assert_nil assigns(:issue_count_by_group)
179 end
176 end
180
177
181 def test_index_with_query_grouped_by_tracker
178 def test_index_with_query_grouped_by_tracker
182 get :index, :project_id => 1, :query_id => 6
179 get :index, :project_id => 1, :query_id => 6
183 assert_response :success
180 assert_response :success
184 assert_template 'index.rhtml'
181 assert_template 'index.rhtml'
185 assert_not_nil assigns(:issues)
182 assert_not_nil assigns(:issues)
186 assert_not_nil assigns(:issue_count_by_group)
183 assert_not_nil assigns(:issue_count_by_group)
187 end
184 end
188
185
189 def test_index_with_query_grouped_by_list_custom_field
186 def test_index_with_query_grouped_by_list_custom_field
190 get :index, :project_id => 1, :query_id => 9
187 get :index, :project_id => 1, :query_id => 9
191 assert_response :success
188 assert_response :success
192 assert_template 'index.rhtml'
189 assert_template 'index.rhtml'
193 assert_not_nil assigns(:issues)
190 assert_not_nil assigns(:issues)
194 assert_not_nil assigns(:issue_count_by_group)
191 assert_not_nil assigns(:issue_count_by_group)
195 end
192 end
193
194 def test_private_query_should_not_be_available_to_other_users
195 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
196 @request.session[:user_id] = 3
197
198 get :index, :query_id => q.id
199 assert_response 403
200 end
201
202 def test_private_query_should_be_available_to_its_user
203 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
204 @request.session[:user_id] = 2
205
206 get :index, :query_id => q.id
207 assert_response :success
208 end
209
210 def test_public_query_should_be_available_to_other_users
211 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
212 @request.session[:user_id] = 3
213
214 get :index, :query_id => q.id
215 assert_response :success
216 end
196
217
197 def test_index_sort_by_field_not_included_in_columns
218 def test_index_sort_by_field_not_included_in_columns
198 Setting.issue_list_default_columns = %w(subject author)
219 Setting.issue_list_default_columns = %w(subject author)
199 get :index, :sort => 'tracker'
220 get :index, :sort => 'tracker'
200 end
221 end
201
222
202 def test_index_csv_with_project
223 def test_index_csv_with_project
203 Setting.default_language = 'en'
224 Setting.default_language = 'en'
204
225
205 get :index, :format => 'csv'
226 get :index, :format => 'csv'
206 assert_response :success
227 assert_response :success
207 assert_not_nil assigns(:issues)
228 assert_not_nil assigns(:issues)
208 assert_equal 'text/csv', @response.content_type
229 assert_equal 'text/csv', @response.content_type
209 assert @response.body.starts_with?("#,")
230 assert @response.body.starts_with?("#,")
210
231
211 get :index, :project_id => 1, :format => 'csv'
232 get :index, :project_id => 1, :format => 'csv'
212 assert_response :success
233 assert_response :success
213 assert_not_nil assigns(:issues)
234 assert_not_nil assigns(:issues)
214 assert_equal 'text/csv', @response.content_type
235 assert_equal 'text/csv', @response.content_type
215 end
236 end
216
237
217 def test_index_pdf
238 def test_index_pdf
218 get :index, :format => 'pdf'
239 get :index, :format => 'pdf'
219 assert_response :success
240 assert_response :success
220 assert_not_nil assigns(:issues)
241 assert_not_nil assigns(:issues)
221 assert_equal 'application/pdf', @response.content_type
242 assert_equal 'application/pdf', @response.content_type
222
243
223 get :index, :project_id => 1, :format => 'pdf'
244 get :index, :project_id => 1, :format => 'pdf'
224 assert_response :success
245 assert_response :success
225 assert_not_nil assigns(:issues)
246 assert_not_nil assigns(:issues)
226 assert_equal 'application/pdf', @response.content_type
247 assert_equal 'application/pdf', @response.content_type
227
248
228 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
249 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
229 assert_response :success
250 assert_response :success
230 assert_not_nil assigns(:issues)
251 assert_not_nil assigns(:issues)
231 assert_equal 'application/pdf', @response.content_type
252 assert_equal 'application/pdf', @response.content_type
232 end
253 end
233
254
234 def test_index_pdf_with_query_grouped_by_list_custom_field
255 def test_index_pdf_with_query_grouped_by_list_custom_field
235 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
256 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
236 assert_response :success
257 assert_response :success
237 assert_not_nil assigns(:issues)
258 assert_not_nil assigns(:issues)
238 assert_not_nil assigns(:issue_count_by_group)
259 assert_not_nil assigns(:issue_count_by_group)
239 assert_equal 'application/pdf', @response.content_type
260 assert_equal 'application/pdf', @response.content_type
240 end
261 end
241
262
242 def test_index_sort
263 def test_index_sort
243 get :index, :sort => 'tracker,id:desc'
264 get :index, :sort => 'tracker,id:desc'
244 assert_response :success
265 assert_response :success
245
266
246 sort_params = @request.session['issues_index_sort']
267 sort_params = @request.session['issues_index_sort']
247 assert sort_params.is_a?(String)
268 assert sort_params.is_a?(String)
248 assert_equal 'tracker,id:desc', sort_params
269 assert_equal 'tracker,id:desc', sort_params
249
270
250 issues = assigns(:issues)
271 issues = assigns(:issues)
251 assert_not_nil issues
272 assert_not_nil issues
252 assert !issues.empty?
273 assert !issues.empty?
253 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
274 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
254 end
275 end
255
276
256 def test_index_with_columns
277 def test_index_with_columns
257 columns = ['tracker', 'subject', 'assigned_to']
278 columns = ['tracker', 'subject', 'assigned_to']
258 get :index, :set_filter => 1, :c => columns
279 get :index, :set_filter => 1, :c => columns
259 assert_response :success
280 assert_response :success
260
281
261 # query should use specified columns
282 # query should use specified columns
262 query = assigns(:query)
283 query = assigns(:query)
263 assert_kind_of Query, query
284 assert_kind_of Query, query
264 assert_equal columns, query.column_names.map(&:to_s)
285 assert_equal columns, query.column_names.map(&:to_s)
265
286
266 # columns should be stored in session
287 # columns should be stored in session
267 assert_kind_of Hash, session[:query]
288 assert_kind_of Hash, session[:query]
268 assert_kind_of Array, session[:query][:column_names]
289 assert_kind_of Array, session[:query][:column_names]
269 assert_equal columns, session[:query][:column_names].map(&:to_s)
290 assert_equal columns, session[:query][:column_names].map(&:to_s)
270
291
271 # ensure only these columns are kept in the selected columns list
292 # ensure only these columns are kept in the selected columns list
272 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
293 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
273 :children => { :count => 3 }
294 :children => { :count => 3 }
274 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
295 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
275 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
296 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
276 end
297 end
277
298
278 def test_index_with_custom_field_column
299 def test_index_with_custom_field_column
279 columns = %w(tracker subject cf_2)
300 columns = %w(tracker subject cf_2)
280 get :index, :set_filter => 1, :c => columns
301 get :index, :set_filter => 1, :c => columns
281 assert_response :success
302 assert_response :success
282
303
283 # query should use specified columns
304 # query should use specified columns
284 query = assigns(:query)
305 query = assigns(:query)
285 assert_kind_of Query, query
306 assert_kind_of Query, query
286 assert_equal columns, query.column_names.map(&:to_s)
307 assert_equal columns, query.column_names.map(&:to_s)
287
308
288 assert_tag :td,
309 assert_tag :td,
289 :attributes => {:class => 'cf_2 string'},
310 :attributes => {:class => 'cf_2 string'},
290 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
311 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
291 end
312 end
292
313
293 def test_show_by_anonymous
314 def test_show_by_anonymous
294 get :show, :id => 1
315 get :show, :id => 1
295 assert_response :success
316 assert_response :success
296 assert_template 'show.rhtml'
317 assert_template 'show.rhtml'
297 assert_not_nil assigns(:issue)
318 assert_not_nil assigns(:issue)
298 assert_equal Issue.find(1), assigns(:issue)
319 assert_equal Issue.find(1), assigns(:issue)
299
320
300 # anonymous role is allowed to add a note
321 # anonymous role is allowed to add a note
301 assert_tag :tag => 'form',
322 assert_tag :tag => 'form',
302 :descendant => { :tag => 'fieldset',
323 :descendant => { :tag => 'fieldset',
303 :child => { :tag => 'legend',
324 :child => { :tag => 'legend',
304 :content => /Notes/ } }
325 :content => /Notes/ } }
305 end
326 end
306
327
307 def test_show_by_manager
328 def test_show_by_manager
308 @request.session[:user_id] = 2
329 @request.session[:user_id] = 2
309 get :show, :id => 1
330 get :show, :id => 1
310 assert_response :success
331 assert_response :success
311
332
312 assert_tag :tag => 'a',
333 assert_tag :tag => 'a',
313 :content => /Quote/
334 :content => /Quote/
314
335
315 assert_tag :tag => 'form',
336 assert_tag :tag => 'form',
316 :descendant => { :tag => 'fieldset',
337 :descendant => { :tag => 'fieldset',
317 :child => { :tag => 'legend',
338 :child => { :tag => 'legend',
318 :content => /Change properties/ } },
339 :content => /Change properties/ } },
319 :descendant => { :tag => 'fieldset',
340 :descendant => { :tag => 'fieldset',
320 :child => { :tag => 'legend',
341 :child => { :tag => 'legend',
321 :content => /Log time/ } },
342 :content => /Log time/ } },
322 :descendant => { :tag => 'fieldset',
343 :descendant => { :tag => 'fieldset',
323 :child => { :tag => 'legend',
344 :child => { :tag => 'legend',
324 :content => /Notes/ } }
345 :content => /Notes/ } }
325 end
346 end
326
347
327 def test_update_form_should_not_display_inactive_enumerations
348 def test_update_form_should_not_display_inactive_enumerations
328 @request.session[:user_id] = 2
349 @request.session[:user_id] = 2
329 get :show, :id => 1
350 get :show, :id => 1
330 assert_response :success
351 assert_response :success
331
352
332 assert ! IssuePriority.find(15).active?
353 assert ! IssuePriority.find(15).active?
333 assert_no_tag :option, :attributes => {:value => '15'},
354 assert_no_tag :option, :attributes => {:value => '15'},
334 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
355 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
335 end
356 end
336
357
337 def test_show_should_deny_anonymous_access_without_permission
358 def test_show_should_deny_anonymous_access_without_permission
338 Role.anonymous.remove_permission!(:view_issues)
359 Role.anonymous.remove_permission!(:view_issues)
339 get :show, :id => 1
360 get :show, :id => 1
340 assert_response :redirect
361 assert_response :redirect
341 end
362 end
342
363
343 def test_show_should_deny_anonymous_access_to_private_issue
364 def test_show_should_deny_anonymous_access_to_private_issue
344 Issue.update_all(["is_private = ?", true], "id = 1")
365 Issue.update_all(["is_private = ?", true], "id = 1")
345 get :show, :id => 1
366 get :show, :id => 1
346 assert_response :redirect
367 assert_response :redirect
347 end
368 end
348
369
349 def test_show_should_deny_non_member_access_without_permission
370 def test_show_should_deny_non_member_access_without_permission
350 Role.non_member.remove_permission!(:view_issues)
371 Role.non_member.remove_permission!(:view_issues)
351 @request.session[:user_id] = 9
372 @request.session[:user_id] = 9
352 get :show, :id => 1
373 get :show, :id => 1
353 assert_response 403
374 assert_response 403
354 end
375 end
355
376
356 def test_show_should_deny_non_member_access_to_private_issue
377 def test_show_should_deny_non_member_access_to_private_issue
357 Issue.update_all(["is_private = ?", true], "id = 1")
378 Issue.update_all(["is_private = ?", true], "id = 1")
358 @request.session[:user_id] = 9
379 @request.session[:user_id] = 9
359 get :show, :id => 1
380 get :show, :id => 1
360 assert_response 403
381 assert_response 403
361 end
382 end
362
383
363 def test_show_should_deny_member_access_without_permission
384 def test_show_should_deny_member_access_without_permission
364 Role.find(1).remove_permission!(:view_issues)
385 Role.find(1).remove_permission!(:view_issues)
365 @request.session[:user_id] = 2
386 @request.session[:user_id] = 2
366 get :show, :id => 1
387 get :show, :id => 1
367 assert_response 403
388 assert_response 403
368 end
389 end
369
390
370 def test_show_should_deny_member_access_to_private_issue_without_permission
391 def test_show_should_deny_member_access_to_private_issue_without_permission
371 Issue.update_all(["is_private = ?", true], "id = 1")
392 Issue.update_all(["is_private = ?", true], "id = 1")
372 @request.session[:user_id] = 3
393 @request.session[:user_id] = 3
373 get :show, :id => 1
394 get :show, :id => 1
374 assert_response 403
395 assert_response 403
375 end
396 end
376
397
377 def test_show_should_allow_author_access_to_private_issue
398 def test_show_should_allow_author_access_to_private_issue
378 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
399 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
379 @request.session[:user_id] = 3
400 @request.session[:user_id] = 3
380 get :show, :id => 1
401 get :show, :id => 1
381 assert_response :success
402 assert_response :success
382 end
403 end
383
404
384 def test_show_should_allow_assignee_access_to_private_issue
405 def test_show_should_allow_assignee_access_to_private_issue
385 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
406 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
386 @request.session[:user_id] = 3
407 @request.session[:user_id] = 3
387 get :show, :id => 1
408 get :show, :id => 1
388 assert_response :success
409 assert_response :success
389 end
410 end
390
411
391 def test_show_should_allow_member_access_to_private_issue_with_permission
412 def test_show_should_allow_member_access_to_private_issue_with_permission
392 Issue.update_all(["is_private = ?", true], "id = 1")
413 Issue.update_all(["is_private = ?", true], "id = 1")
393 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
414 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
394 @request.session[:user_id] = 3
415 @request.session[:user_id] = 3
395 get :show, :id => 1
416 get :show, :id => 1
396 assert_response :success
417 assert_response :success
397 end
418 end
398
419
399 def test_show_should_not_disclose_relations_to_invisible_issues
420 def test_show_should_not_disclose_relations_to_invisible_issues
400 Setting.cross_project_issue_relations = '1'
421 Setting.cross_project_issue_relations = '1'
401 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
422 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
402 # Relation to a private project issue
423 # Relation to a private project issue
403 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
424 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
404
425
405 get :show, :id => 1
426 get :show, :id => 1
406 assert_response :success
427 assert_response :success
407
428
408 assert_tag :div, :attributes => { :id => 'relations' },
429 assert_tag :div, :attributes => { :id => 'relations' },
409 :descendant => { :tag => 'a', :content => /#2$/ }
430 :descendant => { :tag => 'a', :content => /#2$/ }
410 assert_no_tag :div, :attributes => { :id => 'relations' },
431 assert_no_tag :div, :attributes => { :id => 'relations' },
411 :descendant => { :tag => 'a', :content => /#4$/ }
432 :descendant => { :tag => 'a', :content => /#4$/ }
412 end
433 end
413
434
414 def test_show_atom
435 def test_show_atom
415 get :show, :id => 2, :format => 'atom'
436 get :show, :id => 2, :format => 'atom'
416 assert_response :success
437 assert_response :success
417 assert_template 'journals/index.rxml'
438 assert_template 'journals/index.rxml'
418 # Inline image
439 # Inline image
419 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
440 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
420 end
441 end
421
442
422 def test_show_export_to_pdf
443 def test_show_export_to_pdf
423 get :show, :id => 3, :format => 'pdf'
444 get :show, :id => 3, :format => 'pdf'
424 assert_response :success
445 assert_response :success
425 assert_equal 'application/pdf', @response.content_type
446 assert_equal 'application/pdf', @response.content_type
426 assert @response.body.starts_with?('%PDF')
447 assert @response.body.starts_with?('%PDF')
427 assert_not_nil assigns(:issue)
448 assert_not_nil assigns(:issue)
428 end
449 end
429
450
430 def test_get_new
451 def test_get_new
431 @request.session[:user_id] = 2
452 @request.session[:user_id] = 2
432 get :new, :project_id => 1, :tracker_id => 1
453 get :new, :project_id => 1, :tracker_id => 1
433 assert_response :success
454 assert_response :success
434 assert_template 'new'
455 assert_template 'new'
435
456
436 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
457 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
437 :value => 'Default string' }
458 :value => 'Default string' }
438
459
439 # Be sure we don't display inactive IssuePriorities
460 # Be sure we don't display inactive IssuePriorities
440 assert ! IssuePriority.find(15).active?
461 assert ! IssuePriority.find(15).active?
441 assert_no_tag :option, :attributes => {:value => '15'},
462 assert_no_tag :option, :attributes => {:value => '15'},
442 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
463 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
443 end
464 end
444
465
445 def test_get_new_without_tracker_id
466 def test_get_new_without_tracker_id
446 @request.session[:user_id] = 2
467 @request.session[:user_id] = 2
447 get :new, :project_id => 1
468 get :new, :project_id => 1
448 assert_response :success
469 assert_response :success
449 assert_template 'new'
470 assert_template 'new'
450
471
451 issue = assigns(:issue)
472 issue = assigns(:issue)
452 assert_not_nil issue
473 assert_not_nil issue
453 assert_equal Project.find(1).trackers.first, issue.tracker
474 assert_equal Project.find(1).trackers.first, issue.tracker
454 end
475 end
455
476
456 def test_get_new_with_no_default_status_should_display_an_error
477 def test_get_new_with_no_default_status_should_display_an_error
457 @request.session[:user_id] = 2
478 @request.session[:user_id] = 2
458 IssueStatus.delete_all
479 IssueStatus.delete_all
459
480
460 get :new, :project_id => 1
481 get :new, :project_id => 1
461 assert_response 500
482 assert_response 500
462 assert_error_tag :content => /No default issue/
483 assert_error_tag :content => /No default issue/
463 end
484 end
464
485
465 def test_get_new_with_no_tracker_should_display_an_error
486 def test_get_new_with_no_tracker_should_display_an_error
466 @request.session[:user_id] = 2
487 @request.session[:user_id] = 2
467 Tracker.delete_all
488 Tracker.delete_all
468
489
469 get :new, :project_id => 1
490 get :new, :project_id => 1
470 assert_response 500
491 assert_response 500
471 assert_error_tag :content => /No tracker/
492 assert_error_tag :content => /No tracker/
472 end
493 end
473
494
474 def test_update_new_form
495 def test_update_new_form
475 @request.session[:user_id] = 2
496 @request.session[:user_id] = 2
476 xhr :post, :new, :project_id => 1,
497 xhr :post, :new, :project_id => 1,
477 :issue => {:tracker_id => 2,
498 :issue => {:tracker_id => 2,
478 :subject => 'This is the test_new issue',
499 :subject => 'This is the test_new issue',
479 :description => 'This is the description',
500 :description => 'This is the description',
480 :priority_id => 5}
501 :priority_id => 5}
481 assert_response :success
502 assert_response :success
482 assert_template 'attributes'
503 assert_template 'attributes'
483
504
484 issue = assigns(:issue)
505 issue = assigns(:issue)
485 assert_kind_of Issue, issue
506 assert_kind_of Issue, issue
486 assert_equal 1, issue.project_id
507 assert_equal 1, issue.project_id
487 assert_equal 2, issue.tracker_id
508 assert_equal 2, issue.tracker_id
488 assert_equal 'This is the test_new issue', issue.subject
509 assert_equal 'This is the test_new issue', issue.subject
489 end
510 end
490
511
491 def test_post_create
512 def test_post_create
492 @request.session[:user_id] = 2
513 @request.session[:user_id] = 2
493 assert_difference 'Issue.count' do
514 assert_difference 'Issue.count' do
494 post :create, :project_id => 1,
515 post :create, :project_id => 1,
495 :issue => {:tracker_id => 3,
516 :issue => {:tracker_id => 3,
496 :status_id => 2,
517 :status_id => 2,
497 :subject => 'This is the test_new issue',
518 :subject => 'This is the test_new issue',
498 :description => 'This is the description',
519 :description => 'This is the description',
499 :priority_id => 5,
520 :priority_id => 5,
500 :start_date => '2010-11-07',
521 :start_date => '2010-11-07',
501 :estimated_hours => '',
522 :estimated_hours => '',
502 :custom_field_values => {'2' => 'Value for field 2'}}
523 :custom_field_values => {'2' => 'Value for field 2'}}
503 end
524 end
504 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
525 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
505
526
506 issue = Issue.find_by_subject('This is the test_new issue')
527 issue = Issue.find_by_subject('This is the test_new issue')
507 assert_not_nil issue
528 assert_not_nil issue
508 assert_equal 2, issue.author_id
529 assert_equal 2, issue.author_id
509 assert_equal 3, issue.tracker_id
530 assert_equal 3, issue.tracker_id
510 assert_equal 2, issue.status_id
531 assert_equal 2, issue.status_id
511 assert_equal Date.parse('2010-11-07'), issue.start_date
532 assert_equal Date.parse('2010-11-07'), issue.start_date
512 assert_nil issue.estimated_hours
533 assert_nil issue.estimated_hours
513 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
534 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
514 assert_not_nil v
535 assert_not_nil v
515 assert_equal 'Value for field 2', v.value
536 assert_equal 'Value for field 2', v.value
516 end
537 end
517
538
518 def test_post_create_without_start_date
539 def test_post_create_without_start_date
519 @request.session[:user_id] = 2
540 @request.session[:user_id] = 2
520 assert_difference 'Issue.count' do
541 assert_difference 'Issue.count' do
521 post :create, :project_id => 1,
542 post :create, :project_id => 1,
522 :issue => {:tracker_id => 3,
543 :issue => {:tracker_id => 3,
523 :status_id => 2,
544 :status_id => 2,
524 :subject => 'This is the test_new issue',
545 :subject => 'This is the test_new issue',
525 :description => 'This is the description',
546 :description => 'This is the description',
526 :priority_id => 5,
547 :priority_id => 5,
527 :start_date => '',
548 :start_date => '',
528 :estimated_hours => '',
549 :estimated_hours => '',
529 :custom_field_values => {'2' => 'Value for field 2'}}
550 :custom_field_values => {'2' => 'Value for field 2'}}
530 end
551 end
531 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
552 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
532
553
533 issue = Issue.find_by_subject('This is the test_new issue')
554 issue = Issue.find_by_subject('This is the test_new issue')
534 assert_not_nil issue
555 assert_not_nil issue
535 assert_nil issue.start_date
556 assert_nil issue.start_date
536 end
557 end
537
558
538 def test_post_create_and_continue
559 def test_post_create_and_continue
539 @request.session[:user_id] = 2
560 @request.session[:user_id] = 2
540 post :create, :project_id => 1,
561 post :create, :project_id => 1,
541 :issue => {:tracker_id => 3,
562 :issue => {:tracker_id => 3,
542 :subject => 'This is first issue',
563 :subject => 'This is first issue',
543 :priority_id => 5},
564 :priority_id => 5},
544 :continue => ''
565 :continue => ''
545 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook',
566 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook',
546 :issue => {:tracker_id => 3}
567 :issue => {:tracker_id => 3}
547 end
568 end
548
569
549 def test_post_create_without_custom_fields_param
570 def test_post_create_without_custom_fields_param
550 @request.session[:user_id] = 2
571 @request.session[:user_id] = 2
551 assert_difference 'Issue.count' do
572 assert_difference 'Issue.count' do
552 post :create, :project_id => 1,
573 post :create, :project_id => 1,
553 :issue => {:tracker_id => 1,
574 :issue => {:tracker_id => 1,
554 :subject => 'This is the test_new issue',
575 :subject => 'This is the test_new issue',
555 :description => 'This is the description',
576 :description => 'This is the description',
556 :priority_id => 5}
577 :priority_id => 5}
557 end
578 end
558 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
579 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
559 end
580 end
560
581
561 def test_post_create_with_required_custom_field_and_without_custom_fields_param
582 def test_post_create_with_required_custom_field_and_without_custom_fields_param
562 field = IssueCustomField.find_by_name('Database')
583 field = IssueCustomField.find_by_name('Database')
563 field.update_attribute(:is_required, true)
584 field.update_attribute(:is_required, true)
564
585
565 @request.session[:user_id] = 2
586 @request.session[:user_id] = 2
566 post :create, :project_id => 1,
587 post :create, :project_id => 1,
567 :issue => {:tracker_id => 1,
588 :issue => {:tracker_id => 1,
568 :subject => 'This is the test_new issue',
589 :subject => 'This is the test_new issue',
569 :description => 'This is the description',
590 :description => 'This is the description',
570 :priority_id => 5}
591 :priority_id => 5}
571 assert_response :success
592 assert_response :success
572 assert_template 'new'
593 assert_template 'new'
573 issue = assigns(:issue)
594 issue = assigns(:issue)
574 assert_not_nil issue
595 assert_not_nil issue
575 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
596 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
576 end
597 end
577
598
578 def test_post_create_with_watchers
599 def test_post_create_with_watchers
579 @request.session[:user_id] = 2
600 @request.session[:user_id] = 2
580 ActionMailer::Base.deliveries.clear
601 ActionMailer::Base.deliveries.clear
581
602
582 assert_difference 'Watcher.count', 2 do
603 assert_difference 'Watcher.count', 2 do
583 post :create, :project_id => 1,
604 post :create, :project_id => 1,
584 :issue => {:tracker_id => 1,
605 :issue => {:tracker_id => 1,
585 :subject => 'This is a new issue with watchers',
606 :subject => 'This is a new issue with watchers',
586 :description => 'This is the description',
607 :description => 'This is the description',
587 :priority_id => 5,
608 :priority_id => 5,
588 :watcher_user_ids => ['2', '3']}
609 :watcher_user_ids => ['2', '3']}
589 end
610 end
590 issue = Issue.find_by_subject('This is a new issue with watchers')
611 issue = Issue.find_by_subject('This is a new issue with watchers')
591 assert_not_nil issue
612 assert_not_nil issue
592 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
613 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
593
614
594 # Watchers added
615 # Watchers added
595 assert_equal [2, 3], issue.watcher_user_ids.sort
616 assert_equal [2, 3], issue.watcher_user_ids.sort
596 assert issue.watched_by?(User.find(3))
617 assert issue.watched_by?(User.find(3))
597 # Watchers notified
618 # Watchers notified
598 mail = ActionMailer::Base.deliveries.last
619 mail = ActionMailer::Base.deliveries.last
599 assert_kind_of TMail::Mail, mail
620 assert_kind_of TMail::Mail, mail
600 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
621 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
601 end
622 end
602
623
603 def test_post_create_subissue
624 def test_post_create_subissue
604 @request.session[:user_id] = 2
625 @request.session[:user_id] = 2
605
626
606 assert_difference 'Issue.count' do
627 assert_difference 'Issue.count' do
607 post :create, :project_id => 1,
628 post :create, :project_id => 1,
608 :issue => {:tracker_id => 1,
629 :issue => {:tracker_id => 1,
609 :subject => 'This is a child issue',
630 :subject => 'This is a child issue',
610 :parent_issue_id => 2}
631 :parent_issue_id => 2}
611 end
632 end
612 issue = Issue.find_by_subject('This is a child issue')
633 issue = Issue.find_by_subject('This is a child issue')
613 assert_not_nil issue
634 assert_not_nil issue
614 assert_equal Issue.find(2), issue.parent
635 assert_equal Issue.find(2), issue.parent
615 end
636 end
616
637
617 def test_post_create_subissue_with_non_numeric_parent_id
638 def test_post_create_subissue_with_non_numeric_parent_id
618 @request.session[:user_id] = 2
639 @request.session[:user_id] = 2
619
640
620 assert_difference 'Issue.count' do
641 assert_difference 'Issue.count' do
621 post :create, :project_id => 1,
642 post :create, :project_id => 1,
622 :issue => {:tracker_id => 1,
643 :issue => {:tracker_id => 1,
623 :subject => 'This is a child issue',
644 :subject => 'This is a child issue',
624 :parent_issue_id => 'ABC'}
645 :parent_issue_id => 'ABC'}
625 end
646 end
626 issue = Issue.find_by_subject('This is a child issue')
647 issue = Issue.find_by_subject('This is a child issue')
627 assert_not_nil issue
648 assert_not_nil issue
628 assert_nil issue.parent
649 assert_nil issue.parent
629 end
650 end
630
651
631 def test_post_create_private
652 def test_post_create_private
632 @request.session[:user_id] = 2
653 @request.session[:user_id] = 2
633
654
634 assert_difference 'Issue.count' do
655 assert_difference 'Issue.count' do
635 post :create, :project_id => 1,
656 post :create, :project_id => 1,
636 :issue => {:tracker_id => 1,
657 :issue => {:tracker_id => 1,
637 :subject => 'This is a private issue',
658 :subject => 'This is a private issue',
638 :is_private => '1'}
659 :is_private => '1'}
639 end
660 end
640 issue = Issue.first(:order => 'id DESC')
661 issue = Issue.first(:order => 'id DESC')
641 assert issue.is_private?
662 assert issue.is_private?
642 end
663 end
643
664
644 def test_post_create_private_with_set_own_issues_private_permission
665 def test_post_create_private_with_set_own_issues_private_permission
645 role = Role.find(1)
666 role = Role.find(1)
646 role.remove_permission! :set_issues_private
667 role.remove_permission! :set_issues_private
647 role.add_permission! :set_own_issues_private
668 role.add_permission! :set_own_issues_private
648
669
649 @request.session[:user_id] = 2
670 @request.session[:user_id] = 2
650
671
651 assert_difference 'Issue.count' do
672 assert_difference 'Issue.count' do
652 post :create, :project_id => 1,
673 post :create, :project_id => 1,
653 :issue => {:tracker_id => 1,
674 :issue => {:tracker_id => 1,
654 :subject => 'This is a private issue',
675 :subject => 'This is a private issue',
655 :is_private => '1'}
676 :is_private => '1'}
656 end
677 end
657 issue = Issue.first(:order => 'id DESC')
678 issue = Issue.first(:order => 'id DESC')
658 assert issue.is_private?
679 assert issue.is_private?
659 end
680 end
660
681
661 def test_post_create_should_send_a_notification
682 def test_post_create_should_send_a_notification
662 ActionMailer::Base.deliveries.clear
683 ActionMailer::Base.deliveries.clear
663 @request.session[:user_id] = 2
684 @request.session[:user_id] = 2
664 assert_difference 'Issue.count' do
685 assert_difference 'Issue.count' do
665 post :create, :project_id => 1,
686 post :create, :project_id => 1,
666 :issue => {:tracker_id => 3,
687 :issue => {:tracker_id => 3,
667 :subject => 'This is the test_new issue',
688 :subject => 'This is the test_new issue',
668 :description => 'This is the description',
689 :description => 'This is the description',
669 :priority_id => 5,
690 :priority_id => 5,
670 :estimated_hours => '',
691 :estimated_hours => '',
671 :custom_field_values => {'2' => 'Value for field 2'}}
692 :custom_field_values => {'2' => 'Value for field 2'}}
672 end
693 end
673 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
694 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
674
695
675 assert_equal 1, ActionMailer::Base.deliveries.size
696 assert_equal 1, ActionMailer::Base.deliveries.size
676 end
697 end
677
698
678 def test_post_create_should_preserve_fields_values_on_validation_failure
699 def test_post_create_should_preserve_fields_values_on_validation_failure
679 @request.session[:user_id] = 2
700 @request.session[:user_id] = 2
680 post :create, :project_id => 1,
701 post :create, :project_id => 1,
681 :issue => {:tracker_id => 1,
702 :issue => {:tracker_id => 1,
682 # empty subject
703 # empty subject
683 :subject => '',
704 :subject => '',
684 :description => 'This is a description',
705 :description => 'This is a description',
685 :priority_id => 6,
706 :priority_id => 6,
686 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
707 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
687 assert_response :success
708 assert_response :success
688 assert_template 'new'
709 assert_template 'new'
689
710
690 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
711 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
691 :content => 'This is a description'
712 :content => 'This is a description'
692 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
713 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
693 :child => { :tag => 'option', :attributes => { :selected => 'selected',
714 :child => { :tag => 'option', :attributes => { :selected => 'selected',
694 :value => '6' },
715 :value => '6' },
695 :content => 'High' }
716 :content => 'High' }
696 # Custom fields
717 # Custom fields
697 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
718 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
698 :child => { :tag => 'option', :attributes => { :selected => 'selected',
719 :child => { :tag => 'option', :attributes => { :selected => 'selected',
699 :value => 'Oracle' },
720 :value => 'Oracle' },
700 :content => 'Oracle' }
721 :content => 'Oracle' }
701 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
722 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
702 :value => 'Value for field 2'}
723 :value => 'Value for field 2'}
703 end
724 end
704
725
705 def test_post_create_should_ignore_non_safe_attributes
726 def test_post_create_should_ignore_non_safe_attributes
706 @request.session[:user_id] = 2
727 @request.session[:user_id] = 2
707 assert_nothing_raised do
728 assert_nothing_raised do
708 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
729 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
709 end
730 end
710 end
731 end
711
732
712 context "without workflow privilege" do
733 context "without workflow privilege" do
713 setup do
734 setup do
714 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
735 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
715 Role.anonymous.add_permission! :add_issues, :add_issue_notes
736 Role.anonymous.add_permission! :add_issues, :add_issue_notes
716 end
737 end
717
738
718 context "#new" do
739 context "#new" do
719 should "propose default status only" do
740 should "propose default status only" do
720 get :new, :project_id => 1
741 get :new, :project_id => 1
721 assert_response :success
742 assert_response :success
722 assert_template 'new'
743 assert_template 'new'
723 assert_tag :tag => 'select',
744 assert_tag :tag => 'select',
724 :attributes => {:name => 'issue[status_id]'},
745 :attributes => {:name => 'issue[status_id]'},
725 :children => {:count => 1},
746 :children => {:count => 1},
726 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
747 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
727 end
748 end
728
749
729 should "accept default status" do
750 should "accept default status" do
730 assert_difference 'Issue.count' do
751 assert_difference 'Issue.count' do
731 post :create, :project_id => 1,
752 post :create, :project_id => 1,
732 :issue => {:tracker_id => 1,
753 :issue => {:tracker_id => 1,
733 :subject => 'This is an issue',
754 :subject => 'This is an issue',
734 :status_id => 1}
755 :status_id => 1}
735 end
756 end
736 issue = Issue.last(:order => 'id')
757 issue = Issue.last(:order => 'id')
737 assert_equal IssueStatus.default, issue.status
758 assert_equal IssueStatus.default, issue.status
738 end
759 end
739
760
740 should "ignore unauthorized status" do
761 should "ignore unauthorized status" do
741 assert_difference 'Issue.count' do
762 assert_difference 'Issue.count' do
742 post :create, :project_id => 1,
763 post :create, :project_id => 1,
743 :issue => {:tracker_id => 1,
764 :issue => {:tracker_id => 1,
744 :subject => 'This is an issue',
765 :subject => 'This is an issue',
745 :status_id => 3}
766 :status_id => 3}
746 end
767 end
747 issue = Issue.last(:order => 'id')
768 issue = Issue.last(:order => 'id')
748 assert_equal IssueStatus.default, issue.status
769 assert_equal IssueStatus.default, issue.status
749 end
770 end
750 end
771 end
751
772
752 context "#update" do
773 context "#update" do
753 should "ignore status change" do
774 should "ignore status change" do
754 assert_difference 'Journal.count' do
775 assert_difference 'Journal.count' do
755 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
776 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
756 end
777 end
757 assert_equal 1, Issue.find(1).status_id
778 assert_equal 1, Issue.find(1).status_id
758 end
779 end
759
780
760 should "ignore attributes changes" do
781 should "ignore attributes changes" do
761 assert_difference 'Journal.count' do
782 assert_difference 'Journal.count' do
762 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
783 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
763 end
784 end
764 issue = Issue.find(1)
785 issue = Issue.find(1)
765 assert_equal "Can't print recipes", issue.subject
786 assert_equal "Can't print recipes", issue.subject
766 assert_nil issue.assigned_to
787 assert_nil issue.assigned_to
767 end
788 end
768 end
789 end
769 end
790 end
770
791
771 context "with workflow privilege" do
792 context "with workflow privilege" do
772 setup do
793 setup do
773 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
794 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
774 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
795 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
775 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
796 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
776 Role.anonymous.add_permission! :add_issues, :add_issue_notes
797 Role.anonymous.add_permission! :add_issues, :add_issue_notes
777 end
798 end
778
799
779 context "#update" do
800 context "#update" do
780 should "accept authorized status" do
801 should "accept authorized status" do
781 assert_difference 'Journal.count' do
802 assert_difference 'Journal.count' do
782 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
803 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
783 end
804 end
784 assert_equal 3, Issue.find(1).status_id
805 assert_equal 3, Issue.find(1).status_id
785 end
806 end
786
807
787 should "ignore unauthorized status" do
808 should "ignore unauthorized status" do
788 assert_difference 'Journal.count' do
809 assert_difference 'Journal.count' do
789 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
810 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
790 end
811 end
791 assert_equal 1, Issue.find(1).status_id
812 assert_equal 1, Issue.find(1).status_id
792 end
813 end
793
814
794 should "accept authorized attributes changes" do
815 should "accept authorized attributes changes" do
795 assert_difference 'Journal.count' do
816 assert_difference 'Journal.count' do
796 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
817 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
797 end
818 end
798 issue = Issue.find(1)
819 issue = Issue.find(1)
799 assert_equal 2, issue.assigned_to_id
820 assert_equal 2, issue.assigned_to_id
800 end
821 end
801
822
802 should "ignore unauthorized attributes changes" do
823 should "ignore unauthorized attributes changes" do
803 assert_difference 'Journal.count' do
824 assert_difference 'Journal.count' do
804 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
825 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
805 end
826 end
806 issue = Issue.find(1)
827 issue = Issue.find(1)
807 assert_equal "Can't print recipes", issue.subject
828 assert_equal "Can't print recipes", issue.subject
808 end
829 end
809 end
830 end
810
831
811 context "and :edit_issues permission" do
832 context "and :edit_issues permission" do
812 setup do
833 setup do
813 Role.anonymous.add_permission! :add_issues, :edit_issues
834 Role.anonymous.add_permission! :add_issues, :edit_issues
814 end
835 end
815
836
816 should "accept authorized status" do
837 should "accept authorized status" do
817 assert_difference 'Journal.count' do
838 assert_difference 'Journal.count' do
818 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
839 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
819 end
840 end
820 assert_equal 3, Issue.find(1).status_id
841 assert_equal 3, Issue.find(1).status_id
821 end
842 end
822
843
823 should "ignore unauthorized status" do
844 should "ignore unauthorized status" do
824 assert_difference 'Journal.count' do
845 assert_difference 'Journal.count' do
825 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
846 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
826 end
847 end
827 assert_equal 1, Issue.find(1).status_id
848 assert_equal 1, Issue.find(1).status_id
828 end
849 end
829
850
830 should "accept authorized attributes changes" do
851 should "accept authorized attributes changes" do
831 assert_difference 'Journal.count' do
852 assert_difference 'Journal.count' do
832 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
853 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
833 end
854 end
834 issue = Issue.find(1)
855 issue = Issue.find(1)
835 assert_equal "changed", issue.subject
856 assert_equal "changed", issue.subject
836 assert_equal 2, issue.assigned_to_id
857 assert_equal 2, issue.assigned_to_id
837 end
858 end
838 end
859 end
839 end
860 end
840
861
841 def test_copy_issue
862 def test_copy_issue
842 @request.session[:user_id] = 2
863 @request.session[:user_id] = 2
843 get :new, :project_id => 1, :copy_from => 1
864 get :new, :project_id => 1, :copy_from => 1
844 assert_template 'new'
865 assert_template 'new'
845 assert_not_nil assigns(:issue)
866 assert_not_nil assigns(:issue)
846 orig = Issue.find(1)
867 orig = Issue.find(1)
847 assert_equal orig.subject, assigns(:issue).subject
868 assert_equal orig.subject, assigns(:issue).subject
848 end
869 end
849
870
850 def test_get_edit
871 def test_get_edit
851 @request.session[:user_id] = 2
872 @request.session[:user_id] = 2
852 get :edit, :id => 1
873 get :edit, :id => 1
853 assert_response :success
874 assert_response :success
854 assert_template 'edit'
875 assert_template 'edit'
855 assert_not_nil assigns(:issue)
876 assert_not_nil assigns(:issue)
856 assert_equal Issue.find(1), assigns(:issue)
877 assert_equal Issue.find(1), assigns(:issue)
857
878
858 # Be sure we don't display inactive IssuePriorities
879 # Be sure we don't display inactive IssuePriorities
859 assert ! IssuePriority.find(15).active?
880 assert ! IssuePriority.find(15).active?
860 assert_no_tag :option, :attributes => {:value => '15'},
881 assert_no_tag :option, :attributes => {:value => '15'},
861 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
882 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
862 end
883 end
863
884
864 def test_get_edit_with_params
885 def test_get_edit_with_params
865 @request.session[:user_id] = 2
886 @request.session[:user_id] = 2
866 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
887 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
867 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
888 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
868 assert_response :success
889 assert_response :success
869 assert_template 'edit'
890 assert_template 'edit'
870
891
871 issue = assigns(:issue)
892 issue = assigns(:issue)
872 assert_not_nil issue
893 assert_not_nil issue
873
894
874 assert_equal 5, issue.status_id
895 assert_equal 5, issue.status_id
875 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
896 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
876 :child => { :tag => 'option',
897 :child => { :tag => 'option',
877 :content => 'Closed',
898 :content => 'Closed',
878 :attributes => { :selected => 'selected' } }
899 :attributes => { :selected => 'selected' } }
879
900
880 assert_equal 7, issue.priority_id
901 assert_equal 7, issue.priority_id
881 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
902 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
882 :child => { :tag => 'option',
903 :child => { :tag => 'option',
883 :content => 'Urgent',
904 :content => 'Urgent',
884 :attributes => { :selected => 'selected' } }
905 :attributes => { :selected => 'selected' } }
885
906
886 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
907 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
887 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
908 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
888 :child => { :tag => 'option',
909 :child => { :tag => 'option',
889 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
910 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
890 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
911 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
891 end
912 end
892
913
893 def test_update_edit_form
914 def test_update_edit_form
894 @request.session[:user_id] = 2
915 @request.session[:user_id] = 2
895 xhr :post, :new, :project_id => 1,
916 xhr :post, :new, :project_id => 1,
896 :id => 1,
917 :id => 1,
897 :issue => {:tracker_id => 2,
918 :issue => {:tracker_id => 2,
898 :subject => 'This is the test_new issue',
919 :subject => 'This is the test_new issue',
899 :description => 'This is the description',
920 :description => 'This is the description',
900 :priority_id => 5}
921 :priority_id => 5}
901 assert_response :success
922 assert_response :success
902 assert_template 'attributes'
923 assert_template 'attributes'
903
924
904 issue = assigns(:issue)
925 issue = assigns(:issue)
905 assert_kind_of Issue, issue
926 assert_kind_of Issue, issue
906 assert_equal 1, issue.id
927 assert_equal 1, issue.id
907 assert_equal 1, issue.project_id
928 assert_equal 1, issue.project_id
908 assert_equal 2, issue.tracker_id
929 assert_equal 2, issue.tracker_id
909 assert_equal 'This is the test_new issue', issue.subject
930 assert_equal 'This is the test_new issue', issue.subject
910 end
931 end
911
932
912 def test_update_using_invalid_http_verbs
933 def test_update_using_invalid_http_verbs
913 @request.session[:user_id] = 2
934 @request.session[:user_id] = 2
914 subject = 'Updated by an invalid http verb'
935 subject = 'Updated by an invalid http verb'
915
936
916 get :update, :id => 1, :issue => {:subject => subject}
937 get :update, :id => 1, :issue => {:subject => subject}
917 assert_not_equal subject, Issue.find(1).subject
938 assert_not_equal subject, Issue.find(1).subject
918
939
919 post :update, :id => 1, :issue => {:subject => subject}
940 post :update, :id => 1, :issue => {:subject => subject}
920 assert_not_equal subject, Issue.find(1).subject
941 assert_not_equal subject, Issue.find(1).subject
921
942
922 delete :update, :id => 1, :issue => {:subject => subject}
943 delete :update, :id => 1, :issue => {:subject => subject}
923 assert_not_equal subject, Issue.find(1).subject
944 assert_not_equal subject, Issue.find(1).subject
924 end
945 end
925
946
926 def test_put_update_without_custom_fields_param
947 def test_put_update_without_custom_fields_param
927 @request.session[:user_id] = 2
948 @request.session[:user_id] = 2
928 ActionMailer::Base.deliveries.clear
949 ActionMailer::Base.deliveries.clear
929
950
930 issue = Issue.find(1)
951 issue = Issue.find(1)
931 assert_equal '125', issue.custom_value_for(2).value
952 assert_equal '125', issue.custom_value_for(2).value
932 old_subject = issue.subject
953 old_subject = issue.subject
933 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
954 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
934
955
935 assert_difference('Journal.count') do
956 assert_difference('Journal.count') do
936 assert_difference('JournalDetail.count', 2) do
957 assert_difference('JournalDetail.count', 2) do
937 put :update, :id => 1, :issue => {:subject => new_subject,
958 put :update, :id => 1, :issue => {:subject => new_subject,
938 :priority_id => '6',
959 :priority_id => '6',
939 :category_id => '1' # no change
960 :category_id => '1' # no change
940 }
961 }
941 end
962 end
942 end
963 end
943 assert_redirected_to :action => 'show', :id => '1'
964 assert_redirected_to :action => 'show', :id => '1'
944 issue.reload
965 issue.reload
945 assert_equal new_subject, issue.subject
966 assert_equal new_subject, issue.subject
946 # Make sure custom fields were not cleared
967 # Make sure custom fields were not cleared
947 assert_equal '125', issue.custom_value_for(2).value
968 assert_equal '125', issue.custom_value_for(2).value
948
969
949 mail = ActionMailer::Base.deliveries.last
970 mail = ActionMailer::Base.deliveries.last
950 assert_kind_of TMail::Mail, mail
971 assert_kind_of TMail::Mail, mail
951 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
972 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
952 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
973 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
953 end
974 end
954
975
955 def test_put_update_with_custom_field_change
976 def test_put_update_with_custom_field_change
956 @request.session[:user_id] = 2
977 @request.session[:user_id] = 2
957 issue = Issue.find(1)
978 issue = Issue.find(1)
958 assert_equal '125', issue.custom_value_for(2).value
979 assert_equal '125', issue.custom_value_for(2).value
959
980
960 assert_difference('Journal.count') do
981 assert_difference('Journal.count') do
961 assert_difference('JournalDetail.count', 3) do
982 assert_difference('JournalDetail.count', 3) do
962 put :update, :id => 1, :issue => {:subject => 'Custom field change',
983 put :update, :id => 1, :issue => {:subject => 'Custom field change',
963 :priority_id => '6',
984 :priority_id => '6',
964 :category_id => '1', # no change
985 :category_id => '1', # no change
965 :custom_field_values => { '2' => 'New custom value' }
986 :custom_field_values => { '2' => 'New custom value' }
966 }
987 }
967 end
988 end
968 end
989 end
969 assert_redirected_to :action => 'show', :id => '1'
990 assert_redirected_to :action => 'show', :id => '1'
970 issue.reload
991 issue.reload
971 assert_equal 'New custom value', issue.custom_value_for(2).value
992 assert_equal 'New custom value', issue.custom_value_for(2).value
972
993
973 mail = ActionMailer::Base.deliveries.last
994 mail = ActionMailer::Base.deliveries.last
974 assert_kind_of TMail::Mail, mail
995 assert_kind_of TMail::Mail, mail
975 assert mail.body.include?("Searchable field changed from 125 to New custom value")
996 assert mail.body.include?("Searchable field changed from 125 to New custom value")
976 end
997 end
977
998
978 def test_put_update_with_status_and_assignee_change
999 def test_put_update_with_status_and_assignee_change
979 issue = Issue.find(1)
1000 issue = Issue.find(1)
980 assert_equal 1, issue.status_id
1001 assert_equal 1, issue.status_id
981 @request.session[:user_id] = 2
1002 @request.session[:user_id] = 2
982 assert_difference('TimeEntry.count', 0) do
1003 assert_difference('TimeEntry.count', 0) do
983 put :update,
1004 put :update,
984 :id => 1,
1005 :id => 1,
985 :issue => { :status_id => 2, :assigned_to_id => 3 },
1006 :issue => { :status_id => 2, :assigned_to_id => 3 },
986 :notes => 'Assigned to dlopper',
1007 :notes => 'Assigned to dlopper',
987 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1008 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
988 end
1009 end
989 assert_redirected_to :action => 'show', :id => '1'
1010 assert_redirected_to :action => 'show', :id => '1'
990 issue.reload
1011 issue.reload
991 assert_equal 2, issue.status_id
1012 assert_equal 2, issue.status_id
992 j = Journal.find(:first, :order => 'id DESC')
1013 j = Journal.find(:first, :order => 'id DESC')
993 assert_equal 'Assigned to dlopper', j.notes
1014 assert_equal 'Assigned to dlopper', j.notes
994 assert_equal 2, j.details.size
1015 assert_equal 2, j.details.size
995
1016
996 mail = ActionMailer::Base.deliveries.last
1017 mail = ActionMailer::Base.deliveries.last
997 assert mail.body.include?("Status changed from New to Assigned")
1018 assert mail.body.include?("Status changed from New to Assigned")
998 # subject should contain the new status
1019 # subject should contain the new status
999 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1020 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1000 end
1021 end
1001
1022
1002 def test_put_update_with_note_only
1023 def test_put_update_with_note_only
1003 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1024 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1004 # anonymous user
1025 # anonymous user
1005 put :update,
1026 put :update,
1006 :id => 1,
1027 :id => 1,
1007 :notes => notes
1028 :notes => notes
1008 assert_redirected_to :action => 'show', :id => '1'
1029 assert_redirected_to :action => 'show', :id => '1'
1009 j = Journal.find(:first, :order => 'id DESC')
1030 j = Journal.find(:first, :order => 'id DESC')
1010 assert_equal notes, j.notes
1031 assert_equal notes, j.notes
1011 assert_equal 0, j.details.size
1032 assert_equal 0, j.details.size
1012 assert_equal User.anonymous, j.user
1033 assert_equal User.anonymous, j.user
1013
1034
1014 mail = ActionMailer::Base.deliveries.last
1035 mail = ActionMailer::Base.deliveries.last
1015 assert mail.body.include?(notes)
1036 assert mail.body.include?(notes)
1016 end
1037 end
1017
1038
1018 def test_put_update_with_note_and_spent_time
1039 def test_put_update_with_note_and_spent_time
1019 @request.session[:user_id] = 2
1040 @request.session[:user_id] = 2
1020 spent_hours_before = Issue.find(1).spent_hours
1041 spent_hours_before = Issue.find(1).spent_hours
1021 assert_difference('TimeEntry.count') do
1042 assert_difference('TimeEntry.count') do
1022 put :update,
1043 put :update,
1023 :id => 1,
1044 :id => 1,
1024 :notes => '2.5 hours added',
1045 :notes => '2.5 hours added',
1025 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1046 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1026 end
1047 end
1027 assert_redirected_to :action => 'show', :id => '1'
1048 assert_redirected_to :action => 'show', :id => '1'
1028
1049
1029 issue = Issue.find(1)
1050 issue = Issue.find(1)
1030
1051
1031 j = Journal.find(:first, :order => 'id DESC')
1052 j = Journal.find(:first, :order => 'id DESC')
1032 assert_equal '2.5 hours added', j.notes
1053 assert_equal '2.5 hours added', j.notes
1033 assert_equal 0, j.details.size
1054 assert_equal 0, j.details.size
1034
1055
1035 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1056 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1036 assert_not_nil t
1057 assert_not_nil t
1037 assert_equal 2.5, t.hours
1058 assert_equal 2.5, t.hours
1038 assert_equal spent_hours_before + 2.5, issue.spent_hours
1059 assert_equal spent_hours_before + 2.5, issue.spent_hours
1039 end
1060 end
1040
1061
1041 def test_put_update_with_attachment_only
1062 def test_put_update_with_attachment_only
1042 set_tmp_attachments_directory
1063 set_tmp_attachments_directory
1043
1064
1044 # Delete all fixtured journals, a race condition can occur causing the wrong
1065 # Delete all fixtured journals, a race condition can occur causing the wrong
1045 # journal to get fetched in the next find.
1066 # journal to get fetched in the next find.
1046 Journal.delete_all
1067 Journal.delete_all
1047
1068
1048 # anonymous user
1069 # anonymous user
1049 put :update,
1070 put :update,
1050 :id => 1,
1071 :id => 1,
1051 :notes => '',
1072 :notes => '',
1052 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1073 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1053 assert_redirected_to :action => 'show', :id => '1'
1074 assert_redirected_to :action => 'show', :id => '1'
1054 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1075 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1055 assert j.notes.blank?
1076 assert j.notes.blank?
1056 assert_equal 1, j.details.size
1077 assert_equal 1, j.details.size
1057 assert_equal 'testfile.txt', j.details.first.value
1078 assert_equal 'testfile.txt', j.details.first.value
1058 assert_equal User.anonymous, j.user
1079 assert_equal User.anonymous, j.user
1059
1080
1060 mail = ActionMailer::Base.deliveries.last
1081 mail = ActionMailer::Base.deliveries.last
1061 assert mail.body.include?('testfile.txt')
1082 assert mail.body.include?('testfile.txt')
1062 end
1083 end
1063
1084
1064 def test_put_update_with_attachment_that_fails_to_save
1085 def test_put_update_with_attachment_that_fails_to_save
1065 set_tmp_attachments_directory
1086 set_tmp_attachments_directory
1066
1087
1067 # Delete all fixtured journals, a race condition can occur causing the wrong
1088 # Delete all fixtured journals, a race condition can occur causing the wrong
1068 # journal to get fetched in the next find.
1089 # journal to get fetched in the next find.
1069 Journal.delete_all
1090 Journal.delete_all
1070
1091
1071 # Mock out the unsaved attachment
1092 # Mock out the unsaved attachment
1072 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1093 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1073
1094
1074 # anonymous user
1095 # anonymous user
1075 put :update,
1096 put :update,
1076 :id => 1,
1097 :id => 1,
1077 :notes => '',
1098 :notes => '',
1078 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1099 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1079 assert_redirected_to :action => 'show', :id => '1'
1100 assert_redirected_to :action => 'show', :id => '1'
1080 assert_equal '1 file(s) could not be saved.', flash[:warning]
1101 assert_equal '1 file(s) could not be saved.', flash[:warning]
1081
1102
1082 end if Object.const_defined?(:Mocha)
1103 end if Object.const_defined?(:Mocha)
1083
1104
1084 def test_put_update_with_no_change
1105 def test_put_update_with_no_change
1085 issue = Issue.find(1)
1106 issue = Issue.find(1)
1086 issue.journals.clear
1107 issue.journals.clear
1087 ActionMailer::Base.deliveries.clear
1108 ActionMailer::Base.deliveries.clear
1088
1109
1089 put :update,
1110 put :update,
1090 :id => 1,
1111 :id => 1,
1091 :notes => ''
1112 :notes => ''
1092 assert_redirected_to :action => 'show', :id => '1'
1113 assert_redirected_to :action => 'show', :id => '1'
1093
1114
1094 issue.reload
1115 issue.reload
1095 assert issue.journals.empty?
1116 assert issue.journals.empty?
1096 # No email should be sent
1117 # No email should be sent
1097 assert ActionMailer::Base.deliveries.empty?
1118 assert ActionMailer::Base.deliveries.empty?
1098 end
1119 end
1099
1120
1100 def test_put_update_should_send_a_notification
1121 def test_put_update_should_send_a_notification
1101 @request.session[:user_id] = 2
1122 @request.session[:user_id] = 2
1102 ActionMailer::Base.deliveries.clear
1123 ActionMailer::Base.deliveries.clear
1103 issue = Issue.find(1)
1124 issue = Issue.find(1)
1104 old_subject = issue.subject
1125 old_subject = issue.subject
1105 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1126 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1106
1127
1107 put :update, :id => 1, :issue => {:subject => new_subject,
1128 put :update, :id => 1, :issue => {:subject => new_subject,
1108 :priority_id => '6',
1129 :priority_id => '6',
1109 :category_id => '1' # no change
1130 :category_id => '1' # no change
1110 }
1131 }
1111 assert_equal 1, ActionMailer::Base.deliveries.size
1132 assert_equal 1, ActionMailer::Base.deliveries.size
1112 end
1133 end
1113
1134
1114 def test_put_update_with_invalid_spent_time_hours_only
1135 def test_put_update_with_invalid_spent_time_hours_only
1115 @request.session[:user_id] = 2
1136 @request.session[:user_id] = 2
1116 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1137 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1117
1138
1118 assert_no_difference('Journal.count') do
1139 assert_no_difference('Journal.count') do
1119 put :update,
1140 put :update,
1120 :id => 1,
1141 :id => 1,
1121 :notes => notes,
1142 :notes => notes,
1122 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1143 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1123 end
1144 end
1124 assert_response :success
1145 assert_response :success
1125 assert_template 'edit'
1146 assert_template 'edit'
1126
1147
1127 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1148 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1128 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1149 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1129 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1150 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1130 end
1151 end
1131
1152
1132 def test_put_update_with_invalid_spent_time_comments_only
1153 def test_put_update_with_invalid_spent_time_comments_only
1133 @request.session[:user_id] = 2
1154 @request.session[:user_id] = 2
1134 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1155 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1135
1156
1136 assert_no_difference('Journal.count') do
1157 assert_no_difference('Journal.count') do
1137 put :update,
1158 put :update,
1138 :id => 1,
1159 :id => 1,
1139 :notes => notes,
1160 :notes => notes,
1140 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1161 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1141 end
1162 end
1142 assert_response :success
1163 assert_response :success
1143 assert_template 'edit'
1164 assert_template 'edit'
1144
1165
1145 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1166 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1146 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1167 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1147 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1168 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1148 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1169 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1149 end
1170 end
1150
1171
1151 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1172 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1152 issue = Issue.find(2)
1173 issue = Issue.find(2)
1153 @request.session[:user_id] = 2
1174 @request.session[:user_id] = 2
1154
1175
1155 put :update,
1176 put :update,
1156 :id => issue.id,
1177 :id => issue.id,
1157 :issue => {
1178 :issue => {
1158 :fixed_version_id => 4
1179 :fixed_version_id => 4
1159 }
1180 }
1160
1181
1161 assert_response :redirect
1182 assert_response :redirect
1162 issue.reload
1183 issue.reload
1163 assert_equal 4, issue.fixed_version_id
1184 assert_equal 4, issue.fixed_version_id
1164 assert_not_equal issue.project_id, issue.fixed_version.project_id
1185 assert_not_equal issue.project_id, issue.fixed_version.project_id
1165 end
1186 end
1166
1187
1167 def test_put_update_should_redirect_back_using_the_back_url_parameter
1188 def test_put_update_should_redirect_back_using_the_back_url_parameter
1168 issue = Issue.find(2)
1189 issue = Issue.find(2)
1169 @request.session[:user_id] = 2
1190 @request.session[:user_id] = 2
1170
1191
1171 put :update,
1192 put :update,
1172 :id => issue.id,
1193 :id => issue.id,
1173 :issue => {
1194 :issue => {
1174 :fixed_version_id => 4
1195 :fixed_version_id => 4
1175 },
1196 },
1176 :back_url => '/issues'
1197 :back_url => '/issues'
1177
1198
1178 assert_response :redirect
1199 assert_response :redirect
1179 assert_redirected_to '/issues'
1200 assert_redirected_to '/issues'
1180 end
1201 end
1181
1202
1182 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1203 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1183 issue = Issue.find(2)
1204 issue = Issue.find(2)
1184 @request.session[:user_id] = 2
1205 @request.session[:user_id] = 2
1185
1206
1186 put :update,
1207 put :update,
1187 :id => issue.id,
1208 :id => issue.id,
1188 :issue => {
1209 :issue => {
1189 :fixed_version_id => 4
1210 :fixed_version_id => 4
1190 },
1211 },
1191 :back_url => 'http://google.com'
1212 :back_url => 'http://google.com'
1192
1213
1193 assert_response :redirect
1214 assert_response :redirect
1194 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1215 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1195 end
1216 end
1196
1217
1197 def test_get_bulk_edit
1218 def test_get_bulk_edit
1198 @request.session[:user_id] = 2
1219 @request.session[:user_id] = 2
1199 get :bulk_edit, :ids => [1, 2]
1220 get :bulk_edit, :ids => [1, 2]
1200 assert_response :success
1221 assert_response :success
1201 assert_template 'bulk_edit'
1222 assert_template 'bulk_edit'
1202
1223
1203 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1224 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1204
1225
1205 # Project specific custom field, date type
1226 # Project specific custom field, date type
1206 field = CustomField.find(9)
1227 field = CustomField.find(9)
1207 assert !field.is_for_all?
1228 assert !field.is_for_all?
1208 assert_equal 'date', field.field_format
1229 assert_equal 'date', field.field_format
1209 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1230 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1210
1231
1211 # System wide custom field
1232 # System wide custom field
1212 assert CustomField.find(1).is_for_all?
1233 assert CustomField.find(1).is_for_all?
1213 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1234 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1214
1235
1215 # Be sure we don't display inactive IssuePriorities
1236 # Be sure we don't display inactive IssuePriorities
1216 assert ! IssuePriority.find(15).active?
1237 assert ! IssuePriority.find(15).active?
1217 assert_no_tag :option, :attributes => {:value => '15'},
1238 assert_no_tag :option, :attributes => {:value => '15'},
1218 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1239 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1219 end
1240 end
1220
1241
1221 def test_get_bulk_edit_on_different_projects
1242 def test_get_bulk_edit_on_different_projects
1222 @request.session[:user_id] = 2
1243 @request.session[:user_id] = 2
1223 get :bulk_edit, :ids => [1, 2, 6]
1244 get :bulk_edit, :ids => [1, 2, 6]
1224 assert_response :success
1245 assert_response :success
1225 assert_template 'bulk_edit'
1246 assert_template 'bulk_edit'
1226
1247
1227 # Can not set issues from different projects as children of an issue
1248 # Can not set issues from different projects as children of an issue
1228 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1249 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1229
1250
1230 # Project specific custom field, date type
1251 # Project specific custom field, date type
1231 field = CustomField.find(9)
1252 field = CustomField.find(9)
1232 assert !field.is_for_all?
1253 assert !field.is_for_all?
1233 assert !field.project_ids.include?(Issue.find(6).project_id)
1254 assert !field.project_ids.include?(Issue.find(6).project_id)
1234 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1255 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1235 end
1256 end
1236
1257
1237 def test_get_bulk_edit_with_user_custom_field
1258 def test_get_bulk_edit_with_user_custom_field
1238 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1259 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1239
1260
1240 @request.session[:user_id] = 2
1261 @request.session[:user_id] = 2
1241 get :bulk_edit, :ids => [1, 2]
1262 get :bulk_edit, :ids => [1, 2]
1242 assert_response :success
1263 assert_response :success
1243 assert_template 'bulk_edit'
1264 assert_template 'bulk_edit'
1244
1265
1245 assert_tag :select,
1266 assert_tag :select,
1246 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1267 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1247 :children => {
1268 :children => {
1248 :only => {:tag => 'option'},
1269 :only => {:tag => 'option'},
1249 :count => Project.find(1).users.count + 1
1270 :count => Project.find(1).users.count + 1
1250 }
1271 }
1251 end
1272 end
1252
1273
1253 def test_get_bulk_edit_with_version_custom_field
1274 def test_get_bulk_edit_with_version_custom_field
1254 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1275 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1255
1276
1256 @request.session[:user_id] = 2
1277 @request.session[:user_id] = 2
1257 get :bulk_edit, :ids => [1, 2]
1278 get :bulk_edit, :ids => [1, 2]
1258 assert_response :success
1279 assert_response :success
1259 assert_template 'bulk_edit'
1280 assert_template 'bulk_edit'
1260
1281
1261 assert_tag :select,
1282 assert_tag :select,
1262 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1283 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1263 :children => {
1284 :children => {
1264 :only => {:tag => 'option'},
1285 :only => {:tag => 'option'},
1265 :count => Project.find(1).versions.count + 1
1286 :count => Project.find(1).versions.count + 1
1266 }
1287 }
1267 end
1288 end
1268
1289
1269 def test_bulk_update
1290 def test_bulk_update
1270 @request.session[:user_id] = 2
1291 @request.session[:user_id] = 2
1271 # update issues priority
1292 # update issues priority
1272 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1293 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1273 :issue => {:priority_id => 7,
1294 :issue => {:priority_id => 7,
1274 :assigned_to_id => '',
1295 :assigned_to_id => '',
1275 :custom_field_values => {'2' => ''}}
1296 :custom_field_values => {'2' => ''}}
1276
1297
1277 assert_response 302
1298 assert_response 302
1278 # check that the issues were updated
1299 # check that the issues were updated
1279 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1300 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1280
1301
1281 issue = Issue.find(1)
1302 issue = Issue.find(1)
1282 journal = issue.journals.find(:first, :order => 'created_on DESC')
1303 journal = issue.journals.find(:first, :order => 'created_on DESC')
1283 assert_equal '125', issue.custom_value_for(2).value
1304 assert_equal '125', issue.custom_value_for(2).value
1284 assert_equal 'Bulk editing', journal.notes
1305 assert_equal 'Bulk editing', journal.notes
1285 assert_equal 1, journal.details.size
1306 assert_equal 1, journal.details.size
1286 end
1307 end
1287
1308
1288 def test_bulk_update_on_different_projects
1309 def test_bulk_update_on_different_projects
1289 @request.session[:user_id] = 2
1310 @request.session[:user_id] = 2
1290 # update issues priority
1311 # update issues priority
1291 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1312 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1292 :issue => {:priority_id => 7,
1313 :issue => {:priority_id => 7,
1293 :assigned_to_id => '',
1314 :assigned_to_id => '',
1294 :custom_field_values => {'2' => ''}}
1315 :custom_field_values => {'2' => ''}}
1295
1316
1296 assert_response 302
1317 assert_response 302
1297 # check that the issues were updated
1318 # check that the issues were updated
1298 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1319 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1299
1320
1300 issue = Issue.find(1)
1321 issue = Issue.find(1)
1301 journal = issue.journals.find(:first, :order => 'created_on DESC')
1322 journal = issue.journals.find(:first, :order => 'created_on DESC')
1302 assert_equal '125', issue.custom_value_for(2).value
1323 assert_equal '125', issue.custom_value_for(2).value
1303 assert_equal 'Bulk editing', journal.notes
1324 assert_equal 'Bulk editing', journal.notes
1304 assert_equal 1, journal.details.size
1325 assert_equal 1, journal.details.size
1305 end
1326 end
1306
1327
1307 def test_bulk_update_on_different_projects_without_rights
1328 def test_bulk_update_on_different_projects_without_rights
1308 @request.session[:user_id] = 3
1329 @request.session[:user_id] = 3
1309 user = User.find(3)
1330 user = User.find(3)
1310 action = { :controller => "issues", :action => "bulk_update" }
1331 action = { :controller => "issues", :action => "bulk_update" }
1311 assert user.allowed_to?(action, Issue.find(1).project)
1332 assert user.allowed_to?(action, Issue.find(1).project)
1312 assert ! user.allowed_to?(action, Issue.find(6).project)
1333 assert ! user.allowed_to?(action, Issue.find(6).project)
1313 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1334 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1314 :issue => {:priority_id => 7,
1335 :issue => {:priority_id => 7,
1315 :assigned_to_id => '',
1336 :assigned_to_id => '',
1316 :custom_field_values => {'2' => ''}}
1337 :custom_field_values => {'2' => ''}}
1317 assert_response 403
1338 assert_response 403
1318 assert_not_equal "Bulk should fail", Journal.last.notes
1339 assert_not_equal "Bulk should fail", Journal.last.notes
1319 end
1340 end
1320
1341
1321 def test_bullk_update_should_send_a_notification
1342 def test_bullk_update_should_send_a_notification
1322 @request.session[:user_id] = 2
1343 @request.session[:user_id] = 2
1323 ActionMailer::Base.deliveries.clear
1344 ActionMailer::Base.deliveries.clear
1324 post(:bulk_update,
1345 post(:bulk_update,
1325 {
1346 {
1326 :ids => [1, 2],
1347 :ids => [1, 2],
1327 :notes => 'Bulk editing',
1348 :notes => 'Bulk editing',
1328 :issue => {
1349 :issue => {
1329 :priority_id => 7,
1350 :priority_id => 7,
1330 :assigned_to_id => '',
1351 :assigned_to_id => '',
1331 :custom_field_values => {'2' => ''}
1352 :custom_field_values => {'2' => ''}
1332 }
1353 }
1333 })
1354 })
1334
1355
1335 assert_response 302
1356 assert_response 302
1336 assert_equal 2, ActionMailer::Base.deliveries.size
1357 assert_equal 2, ActionMailer::Base.deliveries.size
1337 end
1358 end
1338
1359
1339 def test_bulk_update_status
1360 def test_bulk_update_status
1340 @request.session[:user_id] = 2
1361 @request.session[:user_id] = 2
1341 # update issues priority
1362 # update issues priority
1342 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1363 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1343 :issue => {:priority_id => '',
1364 :issue => {:priority_id => '',
1344 :assigned_to_id => '',
1365 :assigned_to_id => '',
1345 :status_id => '5'}
1366 :status_id => '5'}
1346
1367
1347 assert_response 302
1368 assert_response 302
1348 issue = Issue.find(1)
1369 issue = Issue.find(1)
1349 assert issue.closed?
1370 assert issue.closed?
1350 end
1371 end
1351
1372
1352 def test_bulk_update_parent_id
1373 def test_bulk_update_parent_id
1353 @request.session[:user_id] = 2
1374 @request.session[:user_id] = 2
1354 post :bulk_update, :ids => [1, 3],
1375 post :bulk_update, :ids => [1, 3],
1355 :notes => 'Bulk editing parent',
1376 :notes => 'Bulk editing parent',
1356 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1377 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1357
1378
1358 assert_response 302
1379 assert_response 302
1359 parent = Issue.find(2)
1380 parent = Issue.find(2)
1360 assert_equal parent.id, Issue.find(1).parent_id
1381 assert_equal parent.id, Issue.find(1).parent_id
1361 assert_equal parent.id, Issue.find(3).parent_id
1382 assert_equal parent.id, Issue.find(3).parent_id
1362 assert_equal [1, 3], parent.children.collect(&:id).sort
1383 assert_equal [1, 3], parent.children.collect(&:id).sort
1363 end
1384 end
1364
1385
1365 def test_bulk_update_custom_field
1386 def test_bulk_update_custom_field
1366 @request.session[:user_id] = 2
1387 @request.session[:user_id] = 2
1367 # update issues priority
1388 # update issues priority
1368 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1389 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1369 :issue => {:priority_id => '',
1390 :issue => {:priority_id => '',
1370 :assigned_to_id => '',
1391 :assigned_to_id => '',
1371 :custom_field_values => {'2' => '777'}}
1392 :custom_field_values => {'2' => '777'}}
1372
1393
1373 assert_response 302
1394 assert_response 302
1374
1395
1375 issue = Issue.find(1)
1396 issue = Issue.find(1)
1376 journal = issue.journals.find(:first, :order => 'created_on DESC')
1397 journal = issue.journals.find(:first, :order => 'created_on DESC')
1377 assert_equal '777', issue.custom_value_for(2).value
1398 assert_equal '777', issue.custom_value_for(2).value
1378 assert_equal 1, journal.details.size
1399 assert_equal 1, journal.details.size
1379 assert_equal '125', journal.details.first.old_value
1400 assert_equal '125', journal.details.first.old_value
1380 assert_equal '777', journal.details.first.value
1401 assert_equal '777', journal.details.first.value
1381 end
1402 end
1382
1403
1383 def test_bulk_update_unassign
1404 def test_bulk_update_unassign
1384 assert_not_nil Issue.find(2).assigned_to
1405 assert_not_nil Issue.find(2).assigned_to
1385 @request.session[:user_id] = 2
1406 @request.session[:user_id] = 2
1386 # unassign issues
1407 # unassign issues
1387 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1408 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1388 assert_response 302
1409 assert_response 302
1389 # check that the issues were updated
1410 # check that the issues were updated
1390 assert_nil Issue.find(2).assigned_to
1411 assert_nil Issue.find(2).assigned_to
1391 end
1412 end
1392
1413
1393 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1414 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1394 @request.session[:user_id] = 2
1415 @request.session[:user_id] = 2
1395
1416
1396 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1417 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1397
1418
1398 assert_response :redirect
1419 assert_response :redirect
1399 issues = Issue.find([1,2])
1420 issues = Issue.find([1,2])
1400 issues.each do |issue|
1421 issues.each do |issue|
1401 assert_equal 4, issue.fixed_version_id
1422 assert_equal 4, issue.fixed_version_id
1402 assert_not_equal issue.project_id, issue.fixed_version.project_id
1423 assert_not_equal issue.project_id, issue.fixed_version.project_id
1403 end
1424 end
1404 end
1425 end
1405
1426
1406 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1427 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1407 @request.session[:user_id] = 2
1428 @request.session[:user_id] = 2
1408 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1429 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1409
1430
1410 assert_response :redirect
1431 assert_response :redirect
1411 assert_redirected_to '/issues'
1432 assert_redirected_to '/issues'
1412 end
1433 end
1413
1434
1414 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1435 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1415 @request.session[:user_id] = 2
1436 @request.session[:user_id] = 2
1416 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1437 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1417
1438
1418 assert_response :redirect
1439 assert_response :redirect
1419 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1440 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1420 end
1441 end
1421
1442
1422 def test_destroy_issue_with_no_time_entries
1443 def test_destroy_issue_with_no_time_entries
1423 assert_nil TimeEntry.find_by_issue_id(2)
1444 assert_nil TimeEntry.find_by_issue_id(2)
1424 @request.session[:user_id] = 2
1445 @request.session[:user_id] = 2
1425 post :destroy, :id => 2
1446 post :destroy, :id => 2
1426 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1447 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1427 assert_nil Issue.find_by_id(2)
1448 assert_nil Issue.find_by_id(2)
1428 end
1449 end
1429
1450
1430 def test_destroy_issues_with_time_entries
1451 def test_destroy_issues_with_time_entries
1431 @request.session[:user_id] = 2
1452 @request.session[:user_id] = 2
1432 post :destroy, :ids => [1, 3]
1453 post :destroy, :ids => [1, 3]
1433 assert_response :success
1454 assert_response :success
1434 assert_template 'destroy'
1455 assert_template 'destroy'
1435 assert_not_nil assigns(:hours)
1456 assert_not_nil assigns(:hours)
1436 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1457 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1437 end
1458 end
1438
1459
1439 def test_destroy_issues_and_destroy_time_entries
1460 def test_destroy_issues_and_destroy_time_entries
1440 @request.session[:user_id] = 2
1461 @request.session[:user_id] = 2
1441 post :destroy, :ids => [1, 3], :todo => 'destroy'
1462 post :destroy, :ids => [1, 3], :todo => 'destroy'
1442 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1463 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1443 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1464 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1444 assert_nil TimeEntry.find_by_id([1, 2])
1465 assert_nil TimeEntry.find_by_id([1, 2])
1445 end
1466 end
1446
1467
1447 def test_destroy_issues_and_assign_time_entries_to_project
1468 def test_destroy_issues_and_assign_time_entries_to_project
1448 @request.session[:user_id] = 2
1469 @request.session[:user_id] = 2
1449 post :destroy, :ids => [1, 3], :todo => 'nullify'
1470 post :destroy, :ids => [1, 3], :todo => 'nullify'
1450 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1471 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1451 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1472 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1452 assert_nil TimeEntry.find(1).issue_id
1473 assert_nil TimeEntry.find(1).issue_id
1453 assert_nil TimeEntry.find(2).issue_id
1474 assert_nil TimeEntry.find(2).issue_id
1454 end
1475 end
1455
1476
1456 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1477 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1457 @request.session[:user_id] = 2
1478 @request.session[:user_id] = 2
1458 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1479 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1459 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1480 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1460 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1481 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1461 assert_equal 2, TimeEntry.find(1).issue_id
1482 assert_equal 2, TimeEntry.find(1).issue_id
1462 assert_equal 2, TimeEntry.find(2).issue_id
1483 assert_equal 2, TimeEntry.find(2).issue_id
1463 end
1484 end
1464
1485
1465 def test_destroy_issues_from_different_projects
1486 def test_destroy_issues_from_different_projects
1466 @request.session[:user_id] = 2
1487 @request.session[:user_id] = 2
1467 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1488 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1468 assert_redirected_to :controller => 'issues', :action => 'index'
1489 assert_redirected_to :controller => 'issues', :action => 'index'
1469 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1490 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1470 end
1491 end
1471
1492
1472 def test_destroy_parent_and_child_issues
1493 def test_destroy_parent_and_child_issues
1473 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1494 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1474 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1495 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1475 assert child.is_descendant_of?(parent.reload)
1496 assert child.is_descendant_of?(parent.reload)
1476
1497
1477 @request.session[:user_id] = 2
1498 @request.session[:user_id] = 2
1478 assert_difference 'Issue.count', -2 do
1499 assert_difference 'Issue.count', -2 do
1479 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1500 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1480 end
1501 end
1481 assert_response 302
1502 assert_response 302
1482 end
1503 end
1483
1504
1484 def test_default_search_scope
1505 def test_default_search_scope
1485 get :index
1506 get :index
1486 assert_tag :div, :attributes => {:id => 'quick-search'},
1507 assert_tag :div, :attributes => {:id => 'quick-search'},
1487 :child => {:tag => 'form',
1508 :child => {:tag => 'form',
1488 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1509 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1489 end
1510 end
1490 end
1511 end
General Comments 0
You need to be logged in to leave comments. Login now