##// END OF EJS Templates
Adds API response to /trackers to get the list of all available trackers (#7181)....
Jean-Philippe Lang -
r7757:053adaef52d7
parent child
Show More
@@ -0,0 +1,8
1 api.array :trackers do
2 @trackers.each do |tracker|
3 api.tracker do
4 api.id tracker.id
5 api.name tracker.name
6 end
7 end
8 end
@@ -0,0 +1,51
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../test_helper', __FILE__)
19
20 class ApiTest::TrackersTest < ActionController::IntegrationTest
21 fixtures :trackers
22
23 def setup
24 Setting.rest_api_enabled = '1'
25 end
26
27 context "/trackers" do
28 context "GET" do
29
30 should "return trackers" do
31 get '/trackers.xml'
32
33 assert_response :success
34 assert_equal 'application/xml', @response.content_type
35 assert_tag :tag => 'trackers',
36 :attributes => {:type => 'array'},
37 :child => {
38 :tag => 'tracker',
39 :child => {
40 :tag => 'id',
41 :content => '2',
42 :sibling => {
43 :tag => 'name',
44 :content => 'Feature request'
45 }
46 }
47 }
48 end
49 end
50 end
51 end
@@ -1,522 +1,535
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
21 class Unauthorized < Exception; end
22
22
23 class ApplicationController < ActionController::Base
23 class ApplicationController < ActionController::Base
24 include Redmine::I18n
24 include Redmine::I18n
25
25
26 layout 'base'
26 layout 'base'
27 exempt_from_layout 'builder', 'rsb'
27 exempt_from_layout 'builder', 'rsb'
28
28
29 protect_from_forgery
29 protect_from_forgery
30 def handle_unverified_request
30 def handle_unverified_request
31 super
31 super
32 cookies.delete(:autologin)
32 cookies.delete(:autologin)
33 end
33 end
34 # Remove broken cookie after upgrade from 0.8.x (#4292)
34 # Remove broken cookie after upgrade from 0.8.x (#4292)
35 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
35 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
36 # TODO: remove it when Rails is fixed
36 # TODO: remove it when Rails is fixed
37 before_filter :delete_broken_cookies
37 before_filter :delete_broken_cookies
38 def delete_broken_cookies
38 def delete_broken_cookies
39 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
39 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
40 cookies.delete '_redmine_session'
40 cookies.delete '_redmine_session'
41 redirect_to home_path
41 redirect_to home_path
42 return false
42 return false
43 end
43 end
44 end
44 end
45
45
46 before_filter :user_setup, :check_if_login_required, :set_localization
46 before_filter :user_setup, :check_if_login_required, :set_localization
47 filter_parameter_logging :password
47 filter_parameter_logging :password
48
48
49 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
49 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
50 rescue_from ::Unauthorized, :with => :deny_access
50 rescue_from ::Unauthorized, :with => :deny_access
51
51
52 include Redmine::Search::Controller
52 include Redmine::Search::Controller
53 include Redmine::MenuManager::MenuController
53 include Redmine::MenuManager::MenuController
54 helper Redmine::MenuManager::MenuHelper
54 helper Redmine::MenuManager::MenuHelper
55
55
56 Redmine::Scm::Base.all.each do |scm|
56 Redmine::Scm::Base.all.each do |scm|
57 require_dependency "repository/#{scm.underscore}"
57 require_dependency "repository/#{scm.underscore}"
58 end
58 end
59
59
60 def user_setup
60 def user_setup
61 # Check the settings cache for each request
61 # Check the settings cache for each request
62 Setting.check_cache
62 Setting.check_cache
63 # Find the current user
63 # Find the current user
64 User.current = find_current_user
64 User.current = find_current_user
65 end
65 end
66
66
67 # Returns the current user or nil if no user is logged in
67 # Returns the current user or nil if no user is logged in
68 # and starts a session if needed
68 # and starts a session if needed
69 def find_current_user
69 def find_current_user
70 if session[:user_id]
70 if session[:user_id]
71 # existing session
71 # existing session
72 (User.active.find(session[:user_id]) rescue nil)
72 (User.active.find(session[:user_id]) rescue nil)
73 elsif cookies[:autologin] && Setting.autologin?
73 elsif cookies[:autologin] && Setting.autologin?
74 # auto-login feature starts a new session
74 # auto-login feature starts a new session
75 user = User.try_to_autologin(cookies[:autologin])
75 user = User.try_to_autologin(cookies[:autologin])
76 session[:user_id] = user.id if user
76 session[:user_id] = user.id if user
77 user
77 user
78 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
78 elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
79 # RSS key authentication does not start a session
79 # RSS key authentication does not start a session
80 User.find_by_rss_key(params[:key])
80 User.find_by_rss_key(params[:key])
81 elsif Setting.rest_api_enabled? && accept_api_auth?
81 elsif Setting.rest_api_enabled? && accept_api_auth?
82 if (key = api_key_from_request)
82 if (key = api_key_from_request)
83 # Use API key
83 # Use API key
84 User.find_by_api_key(key)
84 User.find_by_api_key(key)
85 else
85 else
86 # HTTP Basic, either username/password or API key/random
86 # HTTP Basic, either username/password or API key/random
87 authenticate_with_http_basic do |username, password|
87 authenticate_with_http_basic do |username, password|
88 User.try_to_login(username, password) || User.find_by_api_key(username)
88 User.try_to_login(username, password) || User.find_by_api_key(username)
89 end
89 end
90 end
90 end
91 end
91 end
92 end
92 end
93
93
94 # Sets the logged in user
94 # Sets the logged in user
95 def logged_user=(user)
95 def logged_user=(user)
96 reset_session
96 reset_session
97 if user && user.is_a?(User)
97 if user && user.is_a?(User)
98 User.current = user
98 User.current = user
99 session[:user_id] = user.id
99 session[:user_id] = user.id
100 else
100 else
101 User.current = User.anonymous
101 User.current = User.anonymous
102 end
102 end
103 end
103 end
104
104
105 # check if login is globally required to access the application
105 # check if login is globally required to access the application
106 def check_if_login_required
106 def check_if_login_required
107 # no check needed if user is already logged in
107 # no check needed if user is already logged in
108 return true if User.current.logged?
108 return true if User.current.logged?
109 require_login if Setting.login_required?
109 require_login if Setting.login_required?
110 end
110 end
111
111
112 def set_localization
112 def set_localization
113 lang = nil
113 lang = nil
114 if User.current.logged?
114 if User.current.logged?
115 lang = find_language(User.current.language)
115 lang = find_language(User.current.language)
116 end
116 end
117 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
117 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
118 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
118 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
119 if !accept_lang.blank?
119 if !accept_lang.blank?
120 accept_lang = accept_lang.downcase
120 accept_lang = accept_lang.downcase
121 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
121 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
122 end
122 end
123 end
123 end
124 lang ||= Setting.default_language
124 lang ||= Setting.default_language
125 set_language_if_valid(lang)
125 set_language_if_valid(lang)
126 end
126 end
127
127
128 def require_login
128 def require_login
129 if !User.current.logged?
129 if !User.current.logged?
130 # Extract only the basic url parameters on non-GET requests
130 # Extract only the basic url parameters on non-GET requests
131 if request.get?
131 if request.get?
132 url = url_for(params)
132 url = url_for(params)
133 else
133 else
134 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
134 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
135 end
135 end
136 respond_to do |format|
136 respond_to do |format|
137 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
137 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
138 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
138 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
139 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
139 format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
140 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
140 format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
141 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
141 format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
142 end
142 end
143 return false
143 return false
144 end
144 end
145 true
145 true
146 end
146 end
147
147
148 def require_admin
148 def require_admin
149 return unless require_login
149 return unless require_login
150 if !User.current.admin?
150 if !User.current.admin?
151 render_403
151 render_403
152 return false
152 return false
153 end
153 end
154 true
154 true
155 end
155 end
156
156
157 def deny_access
157 def deny_access
158 User.current.logged? ? render_403 : require_login
158 User.current.logged? ? render_403 : require_login
159 end
159 end
160
160
161 # Authorize the user for the requested action
161 # Authorize the user for the requested action
162 def authorize(ctrl = params[:controller], action = params[:action], global = false)
162 def authorize(ctrl = params[:controller], action = params[:action], global = false)
163 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
163 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global)
164 if allowed
164 if allowed
165 true
165 true
166 else
166 else
167 if @project && @project.archived?
167 if @project && @project.archived?
168 render_403 :message => :notice_not_authorized_archived_project
168 render_403 :message => :notice_not_authorized_archived_project
169 else
169 else
170 deny_access
170 deny_access
171 end
171 end
172 end
172 end
173 end
173 end
174
174
175 # Authorize the user for the requested action outside a project
175 # Authorize the user for the requested action outside a project
176 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
176 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
177 authorize(ctrl, action, global)
177 authorize(ctrl, action, global)
178 end
178 end
179
179
180 # Find project of id params[:id]
180 # Find project of id params[:id]
181 def find_project
181 def find_project
182 @project = Project.find(params[:id])
182 @project = Project.find(params[:id])
183 rescue ActiveRecord::RecordNotFound
183 rescue ActiveRecord::RecordNotFound
184 render_404
184 render_404
185 end
185 end
186
186
187 # Find project of id params[:project_id]
187 # Find project of id params[:project_id]
188 def find_project_by_project_id
188 def find_project_by_project_id
189 @project = Project.find(params[:project_id])
189 @project = Project.find(params[:project_id])
190 rescue ActiveRecord::RecordNotFound
190 rescue ActiveRecord::RecordNotFound
191 render_404
191 render_404
192 end
192 end
193
193
194 # Find a project based on params[:project_id]
194 # Find a project based on params[:project_id]
195 # TODO: some subclasses override this, see about merging their logic
195 # TODO: some subclasses override this, see about merging their logic
196 def find_optional_project
196 def find_optional_project
197 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
197 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
198 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
198 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
199 allowed ? true : deny_access
199 allowed ? true : deny_access
200 rescue ActiveRecord::RecordNotFound
200 rescue ActiveRecord::RecordNotFound
201 render_404
201 render_404
202 end
202 end
203
203
204 # Finds and sets @project based on @object.project
204 # Finds and sets @project based on @object.project
205 def find_project_from_association
205 def find_project_from_association
206 render_404 unless @object.present?
206 render_404 unless @object.present?
207
207
208 @project = @object.project
208 @project = @object.project
209 rescue ActiveRecord::RecordNotFound
209 rescue ActiveRecord::RecordNotFound
210 render_404
210 render_404
211 end
211 end
212
212
213 def find_model_object
213 def find_model_object
214 model = self.class.read_inheritable_attribute('model_object')
214 model = self.class.read_inheritable_attribute('model_object')
215 if model
215 if model
216 @object = model.find(params[:id])
216 @object = model.find(params[:id])
217 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
217 self.instance_variable_set('@' + controller_name.singularize, @object) if @object
218 end
218 end
219 rescue ActiveRecord::RecordNotFound
219 rescue ActiveRecord::RecordNotFound
220 render_404
220 render_404
221 end
221 end
222
222
223 def self.model_object(model)
223 def self.model_object(model)
224 write_inheritable_attribute('model_object', model)
224 write_inheritable_attribute('model_object', model)
225 end
225 end
226
226
227 # Filter for bulk issue operations
227 # Filter for bulk issue operations
228 def find_issues
228 def find_issues
229 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
229 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
230 raise ActiveRecord::RecordNotFound if @issues.empty?
230 raise ActiveRecord::RecordNotFound if @issues.empty?
231 if @issues.detect {|issue| !issue.visible?}
231 if @issues.detect {|issue| !issue.visible?}
232 deny_access
232 deny_access
233 return
233 return
234 end
234 end
235 @projects = @issues.collect(&:project).compact.uniq
235 @projects = @issues.collect(&:project).compact.uniq
236 @project = @projects.first if @projects.size == 1
236 @project = @projects.first if @projects.size == 1
237 rescue ActiveRecord::RecordNotFound
237 rescue ActiveRecord::RecordNotFound
238 render_404
238 render_404
239 end
239 end
240
240
241 # Check if project is unique before bulk operations
241 # Check if project is unique before bulk operations
242 def check_project_uniqueness
242 def check_project_uniqueness
243 unless @project
243 unless @project
244 # TODO: let users bulk edit/move/destroy issues from different projects
244 # TODO: let users bulk edit/move/destroy issues from different projects
245 render_error 'Can not bulk edit/move/destroy issues from different projects'
245 render_error 'Can not bulk edit/move/destroy issues from different projects'
246 return false
246 return false
247 end
247 end
248 end
248 end
249
249
250 # make sure that the user is a member of the project (or admin) if project is private
250 # make sure that the user is a member of the project (or admin) if project is private
251 # used as a before_filter for actions that do not require any particular permission on the project
251 # used as a before_filter for actions that do not require any particular permission on the project
252 def check_project_privacy
252 def check_project_privacy
253 if @project && @project.active?
253 if @project && @project.active?
254 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
254 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
255 true
255 true
256 else
256 else
257 User.current.logged? ? render_403 : require_login
257 User.current.logged? ? render_403 : require_login
258 end
258 end
259 else
259 else
260 @project = nil
260 @project = nil
261 render_404
261 render_404
262 false
262 false
263 end
263 end
264 end
264 end
265
265
266 def back_url
266 def back_url
267 params[:back_url] || request.env['HTTP_REFERER']
267 params[:back_url] || request.env['HTTP_REFERER']
268 end
268 end
269
269
270 def redirect_back_or_default(default)
270 def redirect_back_or_default(default)
271 back_url = CGI.unescape(params[:back_url].to_s)
271 back_url = CGI.unescape(params[:back_url].to_s)
272 if !back_url.blank?
272 if !back_url.blank?
273 begin
273 begin
274 uri = URI.parse(back_url)
274 uri = URI.parse(back_url)
275 # do not redirect user to another host or to the login or register page
275 # do not redirect user to another host or to the login or register page
276 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
276 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
277 redirect_to(back_url)
277 redirect_to(back_url)
278 return
278 return
279 end
279 end
280 rescue URI::InvalidURIError
280 rescue URI::InvalidURIError
281 # redirect to default
281 # redirect to default
282 end
282 end
283 end
283 end
284 redirect_to default
284 redirect_to default
285 false
285 false
286 end
286 end
287
287
288 def render_403(options={})
288 def render_403(options={})
289 @project = nil
289 @project = nil
290 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
290 render_error({:message => :notice_not_authorized, :status => 403}.merge(options))
291 return false
291 return false
292 end
292 end
293
293
294 def render_404(options={})
294 def render_404(options={})
295 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
295 render_error({:message => :notice_file_not_found, :status => 404}.merge(options))
296 return false
296 return false
297 end
297 end
298
298
299 # Renders an error response
299 # Renders an error response
300 def render_error(arg)
300 def render_error(arg)
301 arg = {:message => arg} unless arg.is_a?(Hash)
301 arg = {:message => arg} unless arg.is_a?(Hash)
302
302
303 @message = arg[:message]
303 @message = arg[:message]
304 @message = l(@message) if @message.is_a?(Symbol)
304 @message = l(@message) if @message.is_a?(Symbol)
305 @status = arg[:status] || 500
305 @status = arg[:status] || 500
306
306
307 respond_to do |format|
307 respond_to do |format|
308 format.html {
308 format.html {
309 render :template => 'common/error', :layout => use_layout, :status => @status
309 render :template => 'common/error', :layout => use_layout, :status => @status
310 }
310 }
311 format.atom { head @status }
311 format.atom { head @status }
312 format.xml { head @status }
312 format.xml { head @status }
313 format.js { head @status }
313 format.js { head @status }
314 format.json { head @status }
314 format.json { head @status }
315 end
315 end
316 end
316 end
317
318 # Filter for actions that provide an API response
319 # but have no HTML representation for non admin users
320 def require_admin_or_api_request
321 return true if api_request?
322 if User.current.admin?
323 true
324 elsif User.current.logged?
325 render_error(:status => 406)
326 else
327 deny_access
328 end
329 end
317
330
318 # Picks which layout to use based on the request
331 # Picks which layout to use based on the request
319 #
332 #
320 # @return [boolean, string] name of the layout to use or false for no layout
333 # @return [boolean, string] name of the layout to use or false for no layout
321 def use_layout
334 def use_layout
322 request.xhr? ? false : 'base'
335 request.xhr? ? false : 'base'
323 end
336 end
324
337
325 def invalid_authenticity_token
338 def invalid_authenticity_token
326 if api_request?
339 if api_request?
327 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
340 logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)."
328 end
341 end
329 render_error "Invalid form authenticity token."
342 render_error "Invalid form authenticity token."
330 end
343 end
331
344
332 def render_feed(items, options={})
345 def render_feed(items, options={})
333 @items = items || []
346 @items = items || []
334 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
347 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
335 @items = @items.slice(0, Setting.feeds_limit.to_i)
348 @items = @items.slice(0, Setting.feeds_limit.to_i)
336 @title = options[:title] || Setting.app_title
349 @title = options[:title] || Setting.app_title
337 render :template => "common/feed.atom", :layout => false,
350 render :template => "common/feed.atom", :layout => false,
338 :content_type => 'application/atom+xml'
351 :content_type => 'application/atom+xml'
339 end
352 end
340
353
341 # TODO: remove in Redmine 1.4
354 # TODO: remove in Redmine 1.4
342 def self.accept_key_auth(*actions)
355 def self.accept_key_auth(*actions)
343 ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
356 ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
344 accept_rss_auth(*actions)
357 accept_rss_auth(*actions)
345 end
358 end
346
359
347 # TODO: remove in Redmine 1.4
360 # TODO: remove in Redmine 1.4
348 def accept_key_auth_actions
361 def accept_key_auth_actions
349 ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
362 ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
350 self.class.accept_rss_auth
363 self.class.accept_rss_auth
351 end
364 end
352
365
353 def self.accept_rss_auth(*actions)
366 def self.accept_rss_auth(*actions)
354 if actions.any?
367 if actions.any?
355 write_inheritable_attribute('accept_rss_auth_actions', actions)
368 write_inheritable_attribute('accept_rss_auth_actions', actions)
356 else
369 else
357 read_inheritable_attribute('accept_rss_auth_actions') || []
370 read_inheritable_attribute('accept_rss_auth_actions') || []
358 end
371 end
359 end
372 end
360
373
361 def accept_rss_auth?(action=action_name)
374 def accept_rss_auth?(action=action_name)
362 self.class.accept_rss_auth.include?(action.to_sym)
375 self.class.accept_rss_auth.include?(action.to_sym)
363 end
376 end
364
377
365 def self.accept_api_auth(*actions)
378 def self.accept_api_auth(*actions)
366 if actions.any?
379 if actions.any?
367 write_inheritable_attribute('accept_api_auth_actions', actions)
380 write_inheritable_attribute('accept_api_auth_actions', actions)
368 else
381 else
369 read_inheritable_attribute('accept_api_auth_actions') || []
382 read_inheritable_attribute('accept_api_auth_actions') || []
370 end
383 end
371 end
384 end
372
385
373 def accept_api_auth?(action=action_name)
386 def accept_api_auth?(action=action_name)
374 self.class.accept_api_auth.include?(action.to_sym)
387 self.class.accept_api_auth.include?(action.to_sym)
375 end
388 end
376
389
377 # Returns the number of objects that should be displayed
390 # Returns the number of objects that should be displayed
378 # on the paginated list
391 # on the paginated list
379 def per_page_option
392 def per_page_option
380 per_page = nil
393 per_page = nil
381 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
394 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
382 per_page = params[:per_page].to_s.to_i
395 per_page = params[:per_page].to_s.to_i
383 session[:per_page] = per_page
396 session[:per_page] = per_page
384 elsif session[:per_page]
397 elsif session[:per_page]
385 per_page = session[:per_page]
398 per_page = session[:per_page]
386 else
399 else
387 per_page = Setting.per_page_options_array.first || 25
400 per_page = Setting.per_page_options_array.first || 25
388 end
401 end
389 per_page
402 per_page
390 end
403 end
391
404
392 # Returns offset and limit used to retrieve objects
405 # Returns offset and limit used to retrieve objects
393 # for an API response based on offset, limit and page parameters
406 # for an API response based on offset, limit and page parameters
394 def api_offset_and_limit(options=params)
407 def api_offset_and_limit(options=params)
395 if options[:offset].present?
408 if options[:offset].present?
396 offset = options[:offset].to_i
409 offset = options[:offset].to_i
397 if offset < 0
410 if offset < 0
398 offset = 0
411 offset = 0
399 end
412 end
400 end
413 end
401 limit = options[:limit].to_i
414 limit = options[:limit].to_i
402 if limit < 1
415 if limit < 1
403 limit = 25
416 limit = 25
404 elsif limit > 100
417 elsif limit > 100
405 limit = 100
418 limit = 100
406 end
419 end
407 if offset.nil? && options[:page].present?
420 if offset.nil? && options[:page].present?
408 offset = (options[:page].to_i - 1) * limit
421 offset = (options[:page].to_i - 1) * limit
409 offset = 0 if offset < 0
422 offset = 0 if offset < 0
410 end
423 end
411 offset ||= 0
424 offset ||= 0
412
425
413 [offset, limit]
426 [offset, limit]
414 end
427 end
415
428
416 # qvalues http header parser
429 # qvalues http header parser
417 # code taken from webrick
430 # code taken from webrick
418 def parse_qvalues(value)
431 def parse_qvalues(value)
419 tmp = []
432 tmp = []
420 if value
433 if value
421 parts = value.split(/,\s*/)
434 parts = value.split(/,\s*/)
422 parts.each {|part|
435 parts.each {|part|
423 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
436 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
424 val = m[1]
437 val = m[1]
425 q = (m[2] or 1).to_f
438 q = (m[2] or 1).to_f
426 tmp.push([val, q])
439 tmp.push([val, q])
427 end
440 end
428 }
441 }
429 tmp = tmp.sort_by{|val, q| -q}
442 tmp = tmp.sort_by{|val, q| -q}
430 tmp.collect!{|val, q| val}
443 tmp.collect!{|val, q| val}
431 end
444 end
432 return tmp
445 return tmp
433 rescue
446 rescue
434 nil
447 nil
435 end
448 end
436
449
437 # Returns a string that can be used as filename value in Content-Disposition header
450 # Returns a string that can be used as filename value in Content-Disposition header
438 def filename_for_content_disposition(name)
451 def filename_for_content_disposition(name)
439 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
452 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
440 end
453 end
441
454
442 def api_request?
455 def api_request?
443 %w(xml json).include? params[:format]
456 %w(xml json).include? params[:format]
444 end
457 end
445
458
446 # Returns the API key present in the request
459 # Returns the API key present in the request
447 def api_key_from_request
460 def api_key_from_request
448 if params[:key].present?
461 if params[:key].present?
449 params[:key]
462 params[:key]
450 elsif request.headers["X-Redmine-API-Key"].present?
463 elsif request.headers["X-Redmine-API-Key"].present?
451 request.headers["X-Redmine-API-Key"]
464 request.headers["X-Redmine-API-Key"]
452 end
465 end
453 end
466 end
454
467
455 # Renders a warning flash if obj has unsaved attachments
468 # Renders a warning flash if obj has unsaved attachments
456 def render_attachment_warning_if_needed(obj)
469 def render_attachment_warning_if_needed(obj)
457 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
470 flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
458 end
471 end
459
472
460 # Sets the `flash` notice or error based the number of issues that did not save
473 # Sets the `flash` notice or error based the number of issues that did not save
461 #
474 #
462 # @param [Array, Issue] issues all of the saved and unsaved Issues
475 # @param [Array, Issue] issues all of the saved and unsaved Issues
463 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
476 # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved
464 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
477 def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids)
465 if unsaved_issue_ids.empty?
478 if unsaved_issue_ids.empty?
466 flash[:notice] = l(:notice_successful_update) unless issues.empty?
479 flash[:notice] = l(:notice_successful_update) unless issues.empty?
467 else
480 else
468 flash[:error] = l(:notice_failed_to_save_issues,
481 flash[:error] = l(:notice_failed_to_save_issues,
469 :count => unsaved_issue_ids.size,
482 :count => unsaved_issue_ids.size,
470 :total => issues.size,
483 :total => issues.size,
471 :ids => '#' + unsaved_issue_ids.join(', #'))
484 :ids => '#' + unsaved_issue_ids.join(', #'))
472 end
485 end
473 end
486 end
474
487
475 # Rescues an invalid query statement. Just in case...
488 # Rescues an invalid query statement. Just in case...
476 def query_statement_invalid(exception)
489 def query_statement_invalid(exception)
477 logger.error "Query::StatementInvalid: #{exception.message}" if logger
490 logger.error "Query::StatementInvalid: #{exception.message}" if logger
478 session.delete(:query)
491 session.delete(:query)
479 sort_clear if respond_to?(:sort_clear)
492 sort_clear if respond_to?(:sort_clear)
480 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
493 render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
481 end
494 end
482
495
483 # Converts the errors on an ActiveRecord object into a common JSON format
496 # Converts the errors on an ActiveRecord object into a common JSON format
484 def object_errors_to_json(object)
497 def object_errors_to_json(object)
485 object.errors.collect do |attribute, error|
498 object.errors.collect do |attribute, error|
486 { attribute => error }
499 { attribute => error }
487 end.to_json
500 end.to_json
488 end
501 end
489
502
490 # Renders API response on validation failure
503 # Renders API response on validation failure
491 def render_validation_errors(object)
504 def render_validation_errors(object)
492 options = { :status => :unprocessable_entity, :layout => false }
505 options = { :status => :unprocessable_entity, :layout => false }
493 options.merge!(case params[:format]
506 options.merge!(case params[:format]
494 when 'xml'; { :xml => object.errors }
507 when 'xml'; { :xml => object.errors }
495 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
508 when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance
496 else
509 else
497 raise "Unknown format #{params[:format]} in #render_validation_errors"
510 raise "Unknown format #{params[:format]} in #render_validation_errors"
498 end
511 end
499 )
512 )
500 render options
513 render options
501 end
514 end
502
515
503 # Overrides #default_template so that the api template
516 # Overrides #default_template so that the api template
504 # is used automatically if it exists
517 # is used automatically if it exists
505 def default_template(action_name = self.action_name)
518 def default_template(action_name = self.action_name)
506 if api_request?
519 if api_request?
507 begin
520 begin
508 return self.view_paths.find_template(default_template_name(action_name), 'api')
521 return self.view_paths.find_template(default_template_name(action_name), 'api')
509 rescue ::ActionView::MissingTemplate
522 rescue ::ActionView::MissingTemplate
510 # the api template was not found
523 # the api template was not found
511 # fallback to the default behaviour
524 # fallback to the default behaviour
512 end
525 end
513 end
526 end
514 super
527 super
515 end
528 end
516
529
517 # Overrides #pick_layout so that #render with no arguments
530 # Overrides #pick_layout so that #render with no arguments
518 # doesn't use the layout for api requests
531 # doesn't use the layout for api requests
519 def pick_layout(*args)
532 def pick_layout(*args)
520 api_request? ? nil : super
533 api_request? ? nil : super
521 end
534 end
522 end
535 end
@@ -1,64 +1,73
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 TrackersController < ApplicationController
18 class TrackersController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin
21 before_filter :require_admin, :except => :index
22 before_filter :require_admin_or_api_request, :only => :index
23 accept_api_auth :index
22
24
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :index }
25 verify :method => :post, :only => :destroy, :redirect_to => { :action => :index }
24
26
25 def index
27 def index
26 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
28 respond_to do |format|
27 render :action => "index", :layout => false if request.xhr?
29 format.html {
30 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
31 render :action => "index", :layout => false if request.xhr?
32 }
33 format.api {
34 @trackers = Tracker.all
35 }
36 end
28 end
37 end
29
38
30 def new
39 def new
31 @tracker = Tracker.new(params[:tracker])
40 @tracker = Tracker.new(params[:tracker])
32 if request.post? and @tracker.save
41 if request.post? and @tracker.save
33 # workflow copy
42 # workflow copy
34 if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
43 if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
35 @tracker.workflows.copy(copy_from)
44 @tracker.workflows.copy(copy_from)
36 end
45 end
37 flash[:notice] = l(:notice_successful_create)
46 flash[:notice] = l(:notice_successful_create)
38 redirect_to :action => 'index'
47 redirect_to :action => 'index'
39 return
48 return
40 end
49 end
41 @trackers = Tracker.find :all, :order => 'position'
50 @trackers = Tracker.find :all, :order => 'position'
42 @projects = Project.find(:all)
51 @projects = Project.find(:all)
43 end
52 end
44
53
45 def edit
54 def edit
46 @tracker = Tracker.find(params[:id])
55 @tracker = Tracker.find(params[:id])
47 if request.post? and @tracker.update_attributes(params[:tracker])
56 if request.post? and @tracker.update_attributes(params[:tracker])
48 flash[:notice] = l(:notice_successful_update)
57 flash[:notice] = l(:notice_successful_update)
49 redirect_to :action => 'index'
58 redirect_to :action => 'index'
50 return
59 return
51 end
60 end
52 @projects = Project.find(:all)
61 @projects = Project.find(:all)
53 end
62 end
54
63
55 def destroy
64 def destroy
56 @tracker = Tracker.find(params[:id])
65 @tracker = Tracker.find(params[:id])
57 unless @tracker.issues.empty?
66 unless @tracker.issues.empty?
58 flash[:error] = l(:error_can_not_delete_tracker)
67 flash[:error] = l(:error_can_not_delete_tracker)
59 else
68 else
60 @tracker.destroy
69 @tracker.destroy
61 end
70 end
62 redirect_to :action => 'index'
71 redirect_to :action => 'index'
63 end
72 end
64 end
73 end
@@ -1,256 +1,257
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signin 'login', :controller => 'account', :action => 'login'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
13
13
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
16
16
17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
20 time_report.connect 'projects/:project_id/time_entries/report'
20 time_report.connect 'projects/:project_id/time_entries/report'
21 time_report.connect 'projects/:project_id/time_entries/report.:format'
21 time_report.connect 'projects/:project_id/time_entries/report.:format'
22 time_report.connect 'time_entries/report'
22 time_report.connect 'time_entries/report'
23 time_report.connect 'time_entries/report.:format'
23 time_report.connect 'time_entries/report.:format'
24 end
24 end
25
25
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
30 map.time_entries_context_menu '/time_entries/context_menu',
30 map.time_entries_context_menu '/time_entries/context_menu',
31 :controller => 'context_menus', :action => 'time_entries'
31 :controller => 'context_menus', :action => 'time_entries'
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
33 map.resources :time_entries, :controller => 'timelog'
33 map.resources :time_entries, :controller => 'timelog'
34
34
35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
38
38
39 map.with_options :controller => 'messages' do |messages_routes|
39 map.with_options :controller => 'messages' do |messages_routes|
40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
44 end
44 end
45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
49 end
49 end
50 end
50 end
51
51
52 map.with_options :controller => 'boards' do |board_routes|
52 map.with_options :controller => 'boards' do |board_routes|
53 board_routes.with_options :conditions => {:method => :get} do |board_views|
53 board_routes.with_options :conditions => {:method => :get} do |board_views|
54 board_views.connect 'projects/:project_id/boards', :action => 'index'
54 board_views.connect 'projects/:project_id/boards', :action => 'index'
55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
59 end
59 end
60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
63 end
63 end
64 end
64 end
65
65
66 map.with_options :controller => 'documents' do |document_routes|
66 map.with_options :controller => 'documents' do |document_routes|
67 document_routes.with_options :conditions => {:method => :get} do |document_views|
67 document_routes.with_options :conditions => {:method => :get} do |document_views|
68 document_views.connect 'projects/:project_id/documents', :action => 'index'
68 document_views.connect 'projects/:project_id/documents', :action => 'index'
69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
70 document_views.connect 'documents/:id', :action => 'show'
70 document_views.connect 'documents/:id', :action => 'show'
71 document_views.connect 'documents/:id/edit', :action => 'edit'
71 document_views.connect 'documents/:id/edit', :action => 'edit'
72 end
72 end
73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
76 end
76 end
77 end
77 end
78
78
79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
80 map.resources :queries, :except => [:show]
80 map.resources :queries, :except => [:show]
81
81
82 # Misc issue routes. TODO: move into resources
82 # Misc issue routes. TODO: move into resources
83 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
83 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
84 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
84 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
85 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
85 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
86 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
86 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
87 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
87 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
88 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
88 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
89 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
89 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
90 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
90 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
91
91
92 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
92 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
93 gantts_routes.connect '/projects/:project_id/issues/gantt'
93 gantts_routes.connect '/projects/:project_id/issues/gantt'
94 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
94 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
95 gantts_routes.connect '/issues/gantt.:format'
95 gantts_routes.connect '/issues/gantt.:format'
96 end
96 end
97
97
98 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
98 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
99 calendars_routes.connect '/projects/:project_id/issues/calendar'
99 calendars_routes.connect '/projects/:project_id/issues/calendar'
100 calendars_routes.connect '/issues/calendar'
100 calendars_routes.connect '/issues/calendar'
101 end
101 end
102
102
103 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
103 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
104 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
104 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
105 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
105 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
106 end
106 end
107
107
108 # Following two routes conflict with the resources because #index allows POST
108 # Following two routes conflict with the resources because #index allows POST
109 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
109 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
111
111
112 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
112 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
113 issues.resources :time_entries, :controller => 'timelog'
113 issues.resources :time_entries, :controller => 'timelog'
114 issues.resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
114 issues.resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
115 end
115 end
116
116
117 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
117 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
118 issues.resources :time_entries, :controller => 'timelog'
118 issues.resources :time_entries, :controller => 'timelog'
119 end
119 end
120
120
121 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
121 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
122
122
123 map.with_options :controller => 'users' do |users|
123 map.with_options :controller => 'users' do |users|
124 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
124 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
125
125
126 users.with_options :conditions => {:method => :post} do |user_actions|
126 users.with_options :conditions => {:method => :post} do |user_actions|
127 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
127 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
129 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
129 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
130 end
130 end
131 end
131 end
132
132
133 map.resources :users, :member => {
133 map.resources :users, :member => {
134 :edit_membership => :post,
134 :edit_membership => :post,
135 :destroy_membership => :post
135 :destroy_membership => :post
136 }
136 }
137
137
138 # For nice "roadmap" in the url for the index action
138 # For nice "roadmap" in the url for the index action
139 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
139 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
140
140
141 map.all_news 'news', :controller => 'news', :action => 'index'
141 map.all_news 'news', :controller => 'news', :action => 'index'
142 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
142 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
143 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
143 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
144 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
144 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
145 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
145 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
146
146
147 map.resources :projects, :member => {
147 map.resources :projects, :member => {
148 :copy => [:get, :post],
148 :copy => [:get, :post],
149 :settings => :get,
149 :settings => :get,
150 :modules => :post,
150 :modules => :post,
151 :archive => :post,
151 :archive => :post,
152 :unarchive => :post
152 :unarchive => :post
153 } do |project|
153 } do |project|
154 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
154 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
155 project.resources :files, :only => [:index, :new, :create]
155 project.resources :files, :only => [:index, :new, :create]
156 project.resources :versions, :shallow => true, :collection => {:close_completed => :put}, :member => {:status_by => :post}
156 project.resources :versions, :shallow => true, :collection => {:close_completed => :put}, :member => {:status_by => :post}
157 project.resources :news, :shallow => true
157 project.resources :news, :shallow => true
158 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
158 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
159 project.resources :queries, :only => [:new, :create]
159 project.resources :queries, :only => [:new, :create]
160
160
161 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
161 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
162 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
162 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
163 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
163 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
164 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
164 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
165 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
165 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
166 project.resources :wiki, :except => [:new, :create], :member => {
166 project.resources :wiki, :except => [:new, :create], :member => {
167 :rename => [:get, :post],
167 :rename => [:get, :post],
168 :history => :get,
168 :history => :get,
169 :preview => :any,
169 :preview => :any,
170 :protect => :post,
170 :protect => :post,
171 :add_attachment => :post
171 :add_attachment => :post
172 }, :collection => {
172 }, :collection => {
173 :export => :get,
173 :export => :get,
174 :date_index => :get
174 :date_index => :get
175 }
175 }
176
176
177 end
177 end
178
178
179 # Destroy uses a get request to prompt the user before the actual DELETE request
179 # Destroy uses a get request to prompt the user before the actual DELETE request
180 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
180 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
181
181
182 # TODO: port to be part of the resources route(s)
182 # TODO: port to be part of the resources route(s)
183 map.with_options :controller => 'projects' do |project_mapper|
183 map.with_options :controller => 'projects' do |project_mapper|
184 project_mapper.with_options :conditions => {:method => :get} do |project_views|
184 project_mapper.with_options :conditions => {:method => :get} do |project_views|
185 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
185 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
186 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
186 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
187 end
187 end
188 end
188 end
189
189
190 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
190 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
191 activity.connect 'projects/:id/activity'
191 activity.connect 'projects/:id/activity'
192 activity.connect 'projects/:id/activity.:format'
192 activity.connect 'projects/:id/activity.:format'
193 activity.connect 'activity', :id => nil
193 activity.connect 'activity', :id => nil
194 activity.connect 'activity.:format', :id => nil
194 activity.connect 'activity.:format', :id => nil
195 end
195 end
196
196
197 map.with_options :controller => 'issue_categories' do |categories|
197 map.with_options :controller => 'issue_categories' do |categories|
198 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
198 categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
199 end
199 end
200
200
201 map.with_options :controller => 'repositories' do |repositories|
201 map.with_options :controller => 'repositories' do |repositories|
202 repositories.with_options :conditions => {:method => :get} do |repository_views|
202 repositories.with_options :conditions => {:method => :get} do |repository_views|
203 repository_views.connect 'projects/:id/repository', :action => 'show'
203 repository_views.connect 'projects/:id/repository', :action => 'show'
204 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
204 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
205 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
205 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
206 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
206 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
207 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
207 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
208 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
208 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
209 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
209 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
210 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
210 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
211 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
211 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
212 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
212 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
213 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
213 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
214 # TODO: why the following route is required?
214 # TODO: why the following route is required?
215 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
215 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
216 repository_views.connect 'projects/:id/repository/:action/*path'
216 repository_views.connect 'projects/:id/repository/:action/*path'
217 end
217 end
218
218
219 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
219 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
220 end
220 end
221
221
222 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
222 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
223 map.connect 'attachments/:id.:format', :controller => 'attachments', :action => 'show', :id => /\d+/
223 map.connect 'attachments/:id.:format', :controller => 'attachments', :action => 'show', :id => /\d+/
224 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
224 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
225 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
225 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
226
226
227 map.resources :groups
227 map.resources :groups
228
228
229 #left old routes at the bottom for backwards compat
229 #left old routes at the bottom for backwards compat
230 map.connect 'trackers.:format', :controller => 'trackers', :action => 'index'
230 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
231 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
231 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
232 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
232 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
233 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
233 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
234 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
234 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
235 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
235 map.connect 'projects/:project_id/news/:action', :controller => 'news'
236 map.connect 'projects/:project_id/news/:action', :controller => 'news'
236 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
237 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
237 map.with_options :controller => 'repositories' do |omap|
238 map.with_options :controller => 'repositories' do |omap|
238 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
239 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
239 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
240 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
240 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
241 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
241 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
242 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
242 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
243 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
243 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
244 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
244 end
245 end
245
246
246 map.with_options :controller => 'sys' do |sys|
247 map.with_options :controller => 'sys' do |sys|
247 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
248 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
248 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
249 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
249 end
250 end
250
251
251 # Install the default route as the lowest priority.
252 # Install the default route as the lowest priority.
252 map.connect ':controller/:action/:id'
253 map.connect ':controller/:action/:id'
253 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
254 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
254 # Used for OpenID
255 # Used for OpenID
255 map.root :controller => 'account', :action => 'login'
256 map.root :controller => 'account', :action => 'login'
256 end
257 end
@@ -1,120 +1,132
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 'trackers_controller'
19 require 'trackers_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class TrackersController; def rescue_action(e) raise e end; end
22 class TrackersController; def rescue_action(e) raise e end; end
23
23
24 class TrackersControllerTest < ActionController::TestCase
24 class TrackersControllerTest < ActionController::TestCase
25 fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields
25 fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields
26
26
27 def setup
27 def setup
28 @controller = TrackersController.new
28 @controller = TrackersController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 @request.session[:user_id] = 1 # admin
32 @request.session[:user_id] = 1 # admin
33 end
33 end
34
34
35 def test_index
35 def test_index
36 get :index
36 get :index
37 assert_response :success
37 assert_response :success
38 assert_template 'index'
38 assert_template 'index'
39 end
39 end
40
41 def test_index_by_anonymous_should_redirect_to_login_form
42 @request.session[:user_id] = nil
43 get :index
44 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftrackers'
45 end
46
47 def test_index_by_user_should_respond_with_406
48 @request.session[:user_id] = 2
49 get :index
50 assert_response 406
51 end
40
52
41 def test_get_new
53 def test_get_new
42 get :new
54 get :new
43 assert_response :success
55 assert_response :success
44 assert_template 'new'
56 assert_template 'new'
45 end
57 end
46
58
47 def test_post_new
59 def test_post_new
48 post :new, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] }
60 post :new, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] }
49 assert_redirected_to :action => 'index'
61 assert_redirected_to :action => 'index'
50 tracker = Tracker.find_by_name('New tracker')
62 tracker = Tracker.find_by_name('New tracker')
51 assert_equal [1], tracker.project_ids.sort
63 assert_equal [1], tracker.project_ids.sort
52 assert_equal [1, 6], tracker.custom_field_ids
64 assert_equal [1, 6], tracker.custom_field_ids
53 assert_equal 0, tracker.workflows.count
65 assert_equal 0, tracker.workflows.count
54 end
66 end
55
67
56 def test_post_new_with_workflow_copy
68 def test_post_new_with_workflow_copy
57 post :new, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1
69 post :new, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1
58 assert_redirected_to :action => 'index'
70 assert_redirected_to :action => 'index'
59 tracker = Tracker.find_by_name('New tracker')
71 tracker = Tracker.find_by_name('New tracker')
60 assert_equal 0, tracker.projects.count
72 assert_equal 0, tracker.projects.count
61 assert_equal Tracker.find(1).workflows.count, tracker.workflows.count
73 assert_equal Tracker.find(1).workflows.count, tracker.workflows.count
62 end
74 end
63
75
64 def test_get_edit
76 def test_get_edit
65 Tracker.find(1).project_ids = [1, 3]
77 Tracker.find(1).project_ids = [1, 3]
66
78
67 get :edit, :id => 1
79 get :edit, :id => 1
68 assert_response :success
80 assert_response :success
69 assert_template 'edit'
81 assert_template 'edit'
70
82
71 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
83 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
72 :value => '1',
84 :value => '1',
73 :checked => 'checked' }
85 :checked => 'checked' }
74
86
75 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
87 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
76 :value => '2',
88 :value => '2',
77 :checked => nil }
89 :checked => nil }
78
90
79 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
91 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
80 :value => '',
92 :value => '',
81 :type => 'hidden'}
93 :type => 'hidden'}
82 end
94 end
83
95
84 def test_post_edit
96 def test_post_edit
85 post :edit, :id => 1, :tracker => { :name => 'Renamed',
97 post :edit, :id => 1, :tracker => { :name => 'Renamed',
86 :project_ids => ['1', '2', ''] }
98 :project_ids => ['1', '2', ''] }
87 assert_redirected_to :action => 'index'
99 assert_redirected_to :action => 'index'
88 assert_equal [1, 2], Tracker.find(1).project_ids.sort
100 assert_equal [1, 2], Tracker.find(1).project_ids.sort
89 end
101 end
90
102
91 def test_post_edit_without_projects
103 def test_post_edit_without_projects
92 post :edit, :id => 1, :tracker => { :name => 'Renamed',
104 post :edit, :id => 1, :tracker => { :name => 'Renamed',
93 :project_ids => [''] }
105 :project_ids => [''] }
94 assert_redirected_to :action => 'index'
106 assert_redirected_to :action => 'index'
95 assert Tracker.find(1).project_ids.empty?
107 assert Tracker.find(1).project_ids.empty?
96 end
108 end
97
109
98 def test_move_lower
110 def test_move_lower
99 tracker = Tracker.find_by_position(1)
111 tracker = Tracker.find_by_position(1)
100 post :edit, :id => 1, :tracker => { :move_to => 'lower' }
112 post :edit, :id => 1, :tracker => { :move_to => 'lower' }
101 assert_equal 2, tracker.reload.position
113 assert_equal 2, tracker.reload.position
102 end
114 end
103
115
104 def test_destroy
116 def test_destroy
105 tracker = Tracker.create!(:name => 'Destroyable')
117 tracker = Tracker.create!(:name => 'Destroyable')
106 assert_difference 'Tracker.count', -1 do
118 assert_difference 'Tracker.count', -1 do
107 post :destroy, :id => tracker.id
119 post :destroy, :id => tracker.id
108 end
120 end
109 assert_redirected_to :action => 'index'
121 assert_redirected_to :action => 'index'
110 assert_nil flash[:error]
122 assert_nil flash[:error]
111 end
123 end
112
124
113 def test_destroy_tracker_in_use
125 def test_destroy_tracker_in_use
114 assert_no_difference 'Tracker.count' do
126 assert_no_difference 'Tracker.count' do
115 post :destroy, :id => 1
127 post :destroy, :id => 1
116 end
128 end
117 assert_redirected_to :action => 'index'
129 assert_redirected_to :action => 'index'
118 assert_not_nil flash[:error]
130 assert_not_nil flash[:error]
119 end
131 end
120 end
132 end
General Comments 0
You need to be logged in to leave comments. Login now