##// END OF EJS Templates
Allow key authentication when creating issues (with tests) #6447...
Eric Davis -
r4251:4b1dd334a596
parent child
Show More
@@ -1,332 +1,332
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
25 before_filter :find_project, :only => [:new, :create]
25 before_filter :find_project, :only => [:new, :create]
26 before_filter :authorize, :except => [:index]
26 before_filter :authorize, :except => [:index]
27 before_filter :find_optional_project, :only => [:index]
27 before_filter :find_optional_project, :only => [:index]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
30 accept_key_auth :index, :show
30 accept_key_auth :index, :show, :create
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :sort
47 helper :sort
48 include SortHelper
48 include SortHelper
49 include IssuesHelper
49 include IssuesHelper
50 helper :timelog
50 helper :timelog
51 helper :gantt
51 helper :gantt
52 include Redmine::Export::PDF
52 include Redmine::Export::PDF
53
53
54 verify :method => [:post, :delete],
54 verify :method => [:post, :delete],
55 :only => :destroy,
55 :only => :destroy,
56 :render => { :nothing => true, :status => :method_not_allowed }
56 :render => { :nothing => true, :status => :method_not_allowed }
57
57
58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
61
61
62 def index
62 def index
63 retrieve_query
63 retrieve_query
64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
65 sort_update(@query.sortable_columns)
65 sort_update(@query.sortable_columns)
66
66
67 if @query.valid?
67 if @query.valid?
68 limit = case params[:format]
68 limit = case params[:format]
69 when 'csv', 'pdf'
69 when 'csv', 'pdf'
70 Setting.issues_export_limit.to_i
70 Setting.issues_export_limit.to_i
71 when 'atom'
71 when 'atom'
72 Setting.feeds_limit.to_i
72 Setting.feeds_limit.to_i
73 else
73 else
74 per_page_option
74 per_page_option
75 end
75 end
76
76
77 @issue_count = @query.issue_count
77 @issue_count = @query.issue_count
78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
80 :order => sort_clause,
80 :order => sort_clause,
81 :offset => @issue_pages.current.offset,
81 :offset => @issue_pages.current.offset,
82 :limit => limit)
82 :limit => limit)
83 @issue_count_by_group = @query.issue_count_by_group
83 @issue_count_by_group = @query.issue_count_by_group
84
84
85 respond_to do |format|
85 respond_to do |format|
86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
87 format.xml { render :layout => false }
87 format.xml { render :layout => false }
88 format.json { render :text => @issues.to_json, :layout => false }
88 format.json { render :text => @issues.to_json, :layout => false }
89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
90 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
90 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
91 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
91 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
92 end
92 end
93 else
93 else
94 # Send html if the query is not valid
94 # Send html if the query is not valid
95 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
95 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
96 end
96 end
97 rescue ActiveRecord::RecordNotFound
97 rescue ActiveRecord::RecordNotFound
98 render_404
98 render_404
99 end
99 end
100
100
101 def show
101 def show
102 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
102 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
103 @journals.each_with_index {|j,i| j.indice = i+1}
103 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105 @changesets = @issue.changesets.visible.all
105 @changesets = @issue.changesets.visible.all
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
106 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
107 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
108 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
108 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
109 @priorities = IssuePriority.all
109 @priorities = IssuePriority.all
110 @time_entry = TimeEntry.new
110 @time_entry = TimeEntry.new
111 respond_to do |format|
111 respond_to do |format|
112 format.html { render :template => 'issues/show.rhtml' }
112 format.html { render :template => 'issues/show.rhtml' }
113 format.xml { render :layout => false }
113 format.xml { render :layout => false }
114 format.json { render :text => @issue.to_json, :layout => false }
114 format.json { render :text => @issue.to_json, :layout => false }
115 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
115 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
116 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
116 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
117 end
117 end
118 end
118 end
119
119
120 # Add a new issue
120 # Add a new issue
121 # The new issue will be created from an existing one if copy_from parameter is given
121 # The new issue will be created from an existing one if copy_from parameter is given
122 def new
122 def new
123 respond_to do |format|
123 respond_to do |format|
124 format.html { render :action => 'new', :layout => !request.xhr? }
124 format.html { render :action => 'new', :layout => !request.xhr? }
125 format.js { render :partial => 'attributes' }
125 format.js { render :partial => 'attributes' }
126 end
126 end
127 end
127 end
128
128
129 def create
129 def create
130 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
130 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
131 if @issue.save
131 if @issue.save
132 attachments = Attachment.attach_files(@issue, params[:attachments])
132 attachments = Attachment.attach_files(@issue, params[:attachments])
133 render_attachment_warning_if_needed(@issue)
133 render_attachment_warning_if_needed(@issue)
134 flash[:notice] = l(:notice_successful_create)
134 flash[:notice] = l(:notice_successful_create)
135 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
135 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
136 respond_to do |format|
136 respond_to do |format|
137 format.html {
137 format.html {
138 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
138 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
139 { :action => 'show', :id => @issue })
139 { :action => 'show', :id => @issue })
140 }
140 }
141 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
141 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }
142 format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
142 format.json { render :text => @issue.to_json, :status => :created, :location => url_for(:controller => 'issues', :action => 'show'), :layout => false }
143 end
143 end
144 return
144 return
145 else
145 else
146 respond_to do |format|
146 respond_to do |format|
147 format.html { render :action => 'new' }
147 format.html { render :action => 'new' }
148 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
148 format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return }
149 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
149 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
150 end
150 end
151 end
151 end
152 end
152 end
153
153
154 # Attributes that can be updated on workflow transition (without :edit permission)
154 # Attributes that can be updated on workflow transition (without :edit permission)
155 # TODO: make it configurable (at least per role)
155 # TODO: make it configurable (at least per role)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
157
157
158 def edit
158 def edit
159 update_issue_from_params
159 update_issue_from_params
160
160
161 @journal = @issue.current_journal
161 @journal = @issue.current_journal
162
162
163 respond_to do |format|
163 respond_to do |format|
164 format.html { }
164 format.html { }
165 format.xml { }
165 format.xml { }
166 end
166 end
167 end
167 end
168
168
169 def update
169 def update
170 update_issue_from_params
170 update_issue_from_params
171
171
172 if @issue.save_issue_with_child_records(params, @time_entry)
172 if @issue.save_issue_with_child_records(params, @time_entry)
173 render_attachment_warning_if_needed(@issue)
173 render_attachment_warning_if_needed(@issue)
174 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
174 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
175
175
176 respond_to do |format|
176 respond_to do |format|
177 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
177 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
178 format.xml { head :ok }
178 format.xml { head :ok }
179 format.json { head :ok }
179 format.json { head :ok }
180 end
180 end
181 else
181 else
182 render_attachment_warning_if_needed(@issue)
182 render_attachment_warning_if_needed(@issue)
183 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
183 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
184 @journal = @issue.current_journal
184 @journal = @issue.current_journal
185
185
186 respond_to do |format|
186 respond_to do |format|
187 format.html { render :action => 'edit' }
187 format.html { render :action => 'edit' }
188 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
188 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
189 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
189 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
190 end
190 end
191 end
191 end
192 end
192 end
193
193
194 # Bulk edit a set of issues
194 # Bulk edit a set of issues
195 def bulk_edit
195 def bulk_edit
196 @issues.sort!
196 @issues.sort!
197 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
197 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
198 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
198 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
199 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
199 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
200 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
200 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
201 end
201 end
202
202
203 def bulk_update
203 def bulk_update
204 @issues.sort!
204 @issues.sort!
205 attributes = parse_params_for_bulk_issue_attributes(params)
205 attributes = parse_params_for_bulk_issue_attributes(params)
206
206
207 unsaved_issue_ids = []
207 unsaved_issue_ids = []
208 @issues.each do |issue|
208 @issues.each do |issue|
209 issue.reload
209 issue.reload
210 journal = issue.init_journal(User.current, params[:notes])
210 journal = issue.init_journal(User.current, params[:notes])
211 issue.safe_attributes = attributes
211 issue.safe_attributes = attributes
212 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
212 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
213 unless issue.save
213 unless issue.save
214 # Keep unsaved issue ids to display them in flash error
214 # Keep unsaved issue ids to display them in flash error
215 unsaved_issue_ids << issue.id
215 unsaved_issue_ids << issue.id
216 end
216 end
217 end
217 end
218 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
218 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
219 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
219 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
220 end
220 end
221
221
222 def destroy
222 def destroy
223 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
223 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
224 if @hours > 0
224 if @hours > 0
225 case params[:todo]
225 case params[:todo]
226 when 'destroy'
226 when 'destroy'
227 # nothing to do
227 # nothing to do
228 when 'nullify'
228 when 'nullify'
229 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
229 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
230 when 'reassign'
230 when 'reassign'
231 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
231 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
232 if reassign_to.nil?
232 if reassign_to.nil?
233 flash.now[:error] = l(:error_issue_not_found_in_project)
233 flash.now[:error] = l(:error_issue_not_found_in_project)
234 return
234 return
235 else
235 else
236 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
236 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
237 end
237 end
238 else
238 else
239 unless params[:format] == 'xml' || params[:format] == 'json'
239 unless params[:format] == 'xml' || params[:format] == 'json'
240 # display the destroy form if it's a user request
240 # display the destroy form if it's a user request
241 return
241 return
242 end
242 end
243 end
243 end
244 end
244 end
245 @issues.each(&:destroy)
245 @issues.each(&:destroy)
246 respond_to do |format|
246 respond_to do |format|
247 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
247 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
248 format.xml { head :ok }
248 format.xml { head :ok }
249 format.json { head :ok }
249 format.json { head :ok }
250 end
250 end
251 end
251 end
252
252
253 private
253 private
254 def find_issue
254 def find_issue
255 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
255 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
256 @project = @issue.project
256 @project = @issue.project
257 rescue ActiveRecord::RecordNotFound
257 rescue ActiveRecord::RecordNotFound
258 render_404
258 render_404
259 end
259 end
260
260
261 def find_project
261 def find_project
262 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
262 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
263 @project = Project.find(project_id)
263 @project = Project.find(project_id)
264 rescue ActiveRecord::RecordNotFound
264 rescue ActiveRecord::RecordNotFound
265 render_404
265 render_404
266 end
266 end
267
267
268 # Used by #edit and #update to set some common instance variables
268 # Used by #edit and #update to set some common instance variables
269 # from the params
269 # from the params
270 # TODO: Refactor, not everything in here is needed by #edit
270 # TODO: Refactor, not everything in here is needed by #edit
271 def update_issue_from_params
271 def update_issue_from_params
272 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
272 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
273 @priorities = IssuePriority.all
273 @priorities = IssuePriority.all
274 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
274 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
275 @time_entry = TimeEntry.new
275 @time_entry = TimeEntry.new
276
276
277 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
277 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
278 @issue.init_journal(User.current, @notes)
278 @issue.init_journal(User.current, @notes)
279 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
279 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
280 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
280 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
281 attrs = params[:issue].dup
281 attrs = params[:issue].dup
282 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
282 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
283 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
283 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
284 @issue.safe_attributes = attrs
284 @issue.safe_attributes = attrs
285 end
285 end
286
286
287 end
287 end
288
288
289 # TODO: Refactor, lots of extra code in here
289 # TODO: Refactor, lots of extra code in here
290 # TODO: Changing tracker on an existing issue should not trigger this
290 # TODO: Changing tracker on an existing issue should not trigger this
291 def build_new_issue_from_params
291 def build_new_issue_from_params
292 if params[:id].blank?
292 if params[:id].blank?
293 @issue = Issue.new
293 @issue = Issue.new
294 @issue.copy_from(params[:copy_from]) if params[:copy_from]
294 @issue.copy_from(params[:copy_from]) if params[:copy_from]
295 @issue.project = @project
295 @issue.project = @project
296 else
296 else
297 @issue = @project.issues.visible.find(params[:id])
297 @issue = @project.issues.visible.find(params[:id])
298 end
298 end
299
299
300 @issue.project = @project
300 @issue.project = @project
301 # Tracker must be set before custom field values
301 # Tracker must be set before custom field values
302 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
302 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
303 if @issue.tracker.nil?
303 if @issue.tracker.nil?
304 render_error l(:error_no_tracker_in_project)
304 render_error l(:error_no_tracker_in_project)
305 return false
305 return false
306 end
306 end
307 if params[:issue].is_a?(Hash)
307 if params[:issue].is_a?(Hash)
308 @issue.safe_attributes = params[:issue]
308 @issue.safe_attributes = params[:issue]
309 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
309 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
310 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
310 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
311 end
311 end
312 end
312 end
313 @issue.author = User.current
313 @issue.author = User.current
314 @issue.start_date ||= Date.today
314 @issue.start_date ||= Date.today
315 @priorities = IssuePriority.all
315 @priorities = IssuePriority.all
316 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
316 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
317 end
317 end
318
318
319 def check_for_default_issue_status
319 def check_for_default_issue_status
320 if IssueStatus.default.nil?
320 if IssueStatus.default.nil?
321 render_error l(:error_no_default_issue_status)
321 render_error l(:error_no_default_issue_status)
322 return false
322 return false
323 end
323 end
324 end
324 end
325
325
326 def parse_params_for_bulk_issue_attributes(params)
326 def parse_params_for_bulk_issue_attributes(params)
327 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
327 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
328 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
328 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
329 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
329 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
330 attributes
330 attributes
331 end
331 end
332 end
332 end
@@ -1,314 +1,315
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 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.dirname(__FILE__)}/../../test_helper"
18 require "#{File.dirname(__FILE__)}/../../test_helper"
19
19
20 class ApiTest::IssuesTest < ActionController::IntegrationTest
20 class ApiTest::IssuesTest < ActionController::IntegrationTest
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :versions,
28 :versions,
29 :trackers,
29 :trackers,
30 :projects_trackers,
30 :projects_trackers,
31 :issue_categories,
31 :issue_categories,
32 :enabled_modules,
32 :enabled_modules,
33 :enumerations,
33 :enumerations,
34 :attachments,
34 :attachments,
35 :workflows,
35 :workflows,
36 :custom_fields,
36 :custom_fields,
37 :custom_values,
37 :custom_values,
38 :custom_fields_projects,
38 :custom_fields_projects,
39 :custom_fields_trackers,
39 :custom_fields_trackers,
40 :time_entries,
40 :time_entries,
41 :journals,
41 :journals,
42 :journal_details,
42 :journal_details,
43 :queries
43 :queries
44
44
45 def setup
45 def setup
46 Setting.rest_api_enabled = '1'
46 Setting.rest_api_enabled = '1'
47 end
47 end
48
48
49 # Use a private project to make sure auth is really working and not just
49 # Use a private project to make sure auth is really working and not just
50 # only showing public issues.
50 # only showing public issues.
51 context "/index.xml" do
51 context "/index.xml" do
52 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
52 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
53 end
53 end
54
54
55 context "/index.json" do
55 context "/index.json" do
56 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
56 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
57 end
57 end
58
58
59 context "/index.xml with filter" do
59 context "/index.xml with filter" do
60 should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
60 should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
61
61
62 should "show only issues with the status_id" do
62 should "show only issues with the status_id" do
63 get '/issues.xml?status_id=5'
63 get '/issues.xml?status_id=5'
64 assert_tag :tag => 'issues',
64 assert_tag :tag => 'issues',
65 :children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
65 :children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
66 :only => { :tag => 'issue' } }
66 :only => { :tag => 'issue' } }
67 end
67 end
68 end
68 end
69
69
70 context "/index.json with filter" do
70 context "/index.json with filter" do
71 should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
71 should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
72
72
73 should "show only issues with the status_id" do
73 should "show only issues with the status_id" do
74 get '/issues.json?status_id=5'
74 get '/issues.json?status_id=5'
75
75
76 json = ActiveSupport::JSON.decode(response.body)
76 json = ActiveSupport::JSON.decode(response.body)
77 status_ids_used = json.collect {|j| j['status_id'] }
77 status_ids_used = json.collect {|j| j['status_id'] }
78 assert_equal 3, status_ids_used.length
78 assert_equal 3, status_ids_used.length
79 assert status_ids_used.all? {|id| id == 5 }
79 assert status_ids_used.all? {|id| id == 5 }
80 end
80 end
81
81
82 end
82 end
83
83
84 # Issue 6 is on a private project
84 # Issue 6 is on a private project
85 context "/issues/6.xml" do
85 context "/issues/6.xml" do
86 should_allow_api_authentication(:get, "/issues/6.xml")
86 should_allow_api_authentication(:get, "/issues/6.xml")
87 end
87 end
88
88
89 context "/issues/6.json" do
89 context "/issues/6.json" do
90 should_allow_api_authentication(:get, "/issues/6.json")
90 should_allow_api_authentication(:get, "/issues/6.json")
91 end
91 end
92
92
93 context "POST /issues.xml" do
93 context "POST /issues.xml" do
94 setup do
94 should_allow_api_authentication(:post,
95 @issue_count = Issue.count
95 '/issues.xml',
96 @attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}
96 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
97 post '/issues.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
97 {:success_code => :created})
98 end
99
100 should_respond_with :created
101 should_respond_with_content_type 'application/xml'
102
98
103 should "create an issue with the attributes" do
99 should "create an issue with the attributes" do
104 assert_equal Issue.count, @issue_count + 1
100 assert_difference('Issue.count') do
105
101 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
106 issue = Issue.first(:order => 'id DESC')
107 @attributes.each do |attribute, value|
108 assert_equal value, issue.send(attribute)
109 end
102 end
103
104 issue = Issue.first(:order => 'id DESC')
105 assert_equal 1, issue.project_id
106 assert_equal 2, issue.tracker_id
107 assert_equal 3, issue.status_id
108 assert_equal 'API test', issue.subject
110 end
109 end
111 end
110 end
112
111
113 context "POST /issues.xml with failure" do
112 context "POST /issues.xml with failure" do
114 setup do
113 should_allow_api_authentication(:post,
115 @attributes = {:project_id => 1}
114 '/issues.xml',
116 post '/issues.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
115 {:issue => {:project_id => 1}},
117 end
116 {:success_code => :unprocessable_entity})
118
119 should_respond_with :unprocessable_entity
120 should_respond_with_content_type 'application/xml'
121
117
122 should "have an errors tag" do
118 should "have an errors tag" do
119 assert_no_difference('Issue.count') do
120 post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
121 end
122
123 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
123 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
124 end
124 end
125 end
125 end
126
126
127 context "POST /issues.json" do
127 context "POST /issues.json" do
128 setup do
128 should_allow_api_authentication(:post,
129 @issue_count = Issue.count
129 '/issues.json',
130 @attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}
130 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
131 post '/issues.json', {:issue => @attributes}, :authorization => credentials('jsmith')
131 {:success_code => :created})
132 end
133
134 should_respond_with :created
135 should_respond_with_content_type 'application/json'
136
132
137 should "create an issue with the attributes" do
133 should "create an issue with the attributes" do
138 assert_equal Issue.count, @issue_count + 1
134 assert_difference('Issue.count') do
139
135 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
140 issue = Issue.first(:order => 'id DESC')
141 @attributes.each do |attribute, value|
142 assert_equal value, issue.send(attribute)
143 end
136 end
137
138 issue = Issue.first(:order => 'id DESC')
139 assert_equal 1, issue.project_id
140 assert_equal 2, issue.tracker_id
141 assert_equal 3, issue.status_id
142 assert_equal 'API test', issue.subject
144 end
143 end
144
145 end
145 end
146
146
147 context "POST /issues.json with failure" do
147 context "POST /issues.json with failure" do
148 setup do
148 should_allow_api_authentication(:post,
149 @attributes = {:project_id => 1}
149 '/issues.json',
150 post '/issues.json', {:issue => @attributes}, :authorization => credentials('jsmith')
150 {:issue => {:project_id => 1}},
151 end
151 {:success_code => :unprocessable_entity})
152
153 should_respond_with :unprocessable_entity
154 should_respond_with_content_type 'application/json'
155
152
156 should "have an errors element" do
153 should "have an errors element" do
154 assert_no_difference('Issue.count') do
155 post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
156 end
157
157 json = ActiveSupport::JSON.decode(response.body)
158 json = ActiveSupport::JSON.decode(response.body)
158 assert_equal "can't be blank", json.first['subject']
159 assert_equal "can't be blank", json.first['subject']
159 end
160 end
160 end
161 end
161
162
162 context "PUT /issues/1.xml" do
163 context "PUT /issues/1.xml" do
163 setup do
164 setup do
164 @issue_count = Issue.count
165 @issue_count = Issue.count
165 @journal_count = Journal.count
166 @journal_count = Journal.count
166 @attributes = {:subject => 'API update', :notes => 'A new note'}
167 @attributes = {:subject => 'API update', :notes => 'A new note'}
167
168
168 put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
169 put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
169 end
170 end
170
171
171 should_respond_with :ok
172 should_respond_with :ok
172 should_respond_with_content_type 'application/xml'
173 should_respond_with_content_type 'application/xml'
173
174
174 should "not create a new issue" do
175 should "not create a new issue" do
175 assert_equal Issue.count, @issue_count
176 assert_equal Issue.count, @issue_count
176 end
177 end
177
178
178 should "create a new journal" do
179 should "create a new journal" do
179 assert_equal Journal.count, @journal_count + 1
180 assert_equal Journal.count, @journal_count + 1
180 end
181 end
181
182
182 should "add the note to the journal" do
183 should "add the note to the journal" do
183 journal = Journal.last
184 journal = Journal.last
184 assert_equal "A new note", journal.notes
185 assert_equal "A new note", journal.notes
185 end
186 end
186
187
187 should "update the issue" do
188 should "update the issue" do
188 issue = Issue.find(1)
189 issue = Issue.find(1)
189 @attributes.each do |attribute, value|
190 @attributes.each do |attribute, value|
190 assert_equal value, issue.send(attribute) unless attribute == :notes
191 assert_equal value, issue.send(attribute) unless attribute == :notes
191 end
192 end
192 end
193 end
193
194
194 end
195 end
195
196
196 context "PUT /issues/1.xml with failed update" do
197 context "PUT /issues/1.xml with failed update" do
197 setup do
198 setup do
198 @attributes = {:subject => ''}
199 @attributes = {:subject => ''}
199 @issue_count = Issue.count
200 @issue_count = Issue.count
200 @journal_count = Journal.count
201 @journal_count = Journal.count
201
202
202 put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
203 put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
203 end
204 end
204
205
205 should_respond_with :unprocessable_entity
206 should_respond_with :unprocessable_entity
206 should_respond_with_content_type 'application/xml'
207 should_respond_with_content_type 'application/xml'
207
208
208 should "not create a new issue" do
209 should "not create a new issue" do
209 assert_equal Issue.count, @issue_count
210 assert_equal Issue.count, @issue_count
210 end
211 end
211
212
212 should "not create a new journal" do
213 should "not create a new journal" do
213 assert_equal Journal.count, @journal_count
214 assert_equal Journal.count, @journal_count
214 end
215 end
215
216
216 should "have an errors tag" do
217 should "have an errors tag" do
217 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
218 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
218 end
219 end
219 end
220 end
220
221
221 context "PUT /issues/1.json" do
222 context "PUT /issues/1.json" do
222 setup do
223 setup do
223 @issue_count = Issue.count
224 @issue_count = Issue.count
224 @journal_count = Journal.count
225 @journal_count = Journal.count
225 @attributes = {:subject => 'API update', :notes => 'A new note'}
226 @attributes = {:subject => 'API update', :notes => 'A new note'}
226
227
227 put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
228 put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
228 end
229 end
229
230
230 should_respond_with :ok
231 should_respond_with :ok
231 should_respond_with_content_type 'application/json'
232 should_respond_with_content_type 'application/json'
232
233
233 should "not create a new issue" do
234 should "not create a new issue" do
234 assert_equal Issue.count, @issue_count
235 assert_equal Issue.count, @issue_count
235 end
236 end
236
237
237 should "create a new journal" do
238 should "create a new journal" do
238 assert_equal Journal.count, @journal_count + 1
239 assert_equal Journal.count, @journal_count + 1
239 end
240 end
240
241
241 should "add the note to the journal" do
242 should "add the note to the journal" do
242 journal = Journal.last
243 journal = Journal.last
243 assert_equal "A new note", journal.notes
244 assert_equal "A new note", journal.notes
244 end
245 end
245
246
246 should "update the issue" do
247 should "update the issue" do
247 issue = Issue.find(1)
248 issue = Issue.find(1)
248 @attributes.each do |attribute, value|
249 @attributes.each do |attribute, value|
249 assert_equal value, issue.send(attribute) unless attribute == :notes
250 assert_equal value, issue.send(attribute) unless attribute == :notes
250 end
251 end
251 end
252 end
252
253
253 end
254 end
254
255
255 context "PUT /issues/1.json with failed update" do
256 context "PUT /issues/1.json with failed update" do
256 setup do
257 setup do
257 @attributes = {:subject => ''}
258 @attributes = {:subject => ''}
258 @issue_count = Issue.count
259 @issue_count = Issue.count
259 @journal_count = Journal.count
260 @journal_count = Journal.count
260
261
261 put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
262 put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
262 end
263 end
263
264
264 should_respond_with :unprocessable_entity
265 should_respond_with :unprocessable_entity
265 should_respond_with_content_type 'application/json'
266 should_respond_with_content_type 'application/json'
266
267
267 should "not create a new issue" do
268 should "not create a new issue" do
268 assert_equal Issue.count, @issue_count
269 assert_equal Issue.count, @issue_count
269 end
270 end
270
271
271 should "not create a new journal" do
272 should "not create a new journal" do
272 assert_equal Journal.count, @journal_count
273 assert_equal Journal.count, @journal_count
273 end
274 end
274
275
275 should "have an errors attribute" do
276 should "have an errors attribute" do
276 json = ActiveSupport::JSON.decode(response.body)
277 json = ActiveSupport::JSON.decode(response.body)
277 assert_equal "can't be blank", json.first['subject']
278 assert_equal "can't be blank", json.first['subject']
278 end
279 end
279 end
280 end
280
281
281 context "DELETE /issues/1.xml" do
282 context "DELETE /issues/1.xml" do
282 setup do
283 setup do
283 @issue_count = Issue.count
284 @issue_count = Issue.count
284 delete '/issues/1.xml', {}, :authorization => credentials('jsmith')
285 delete '/issues/1.xml', {}, :authorization => credentials('jsmith')
285 end
286 end
286
287
287 should_respond_with :ok
288 should_respond_with :ok
288 should_respond_with_content_type 'application/xml'
289 should_respond_with_content_type 'application/xml'
289
290
290 should "delete the issue" do
291 should "delete the issue" do
291 assert_equal Issue.count, @issue_count -1
292 assert_equal Issue.count, @issue_count -1
292 assert_nil Issue.find_by_id(1)
293 assert_nil Issue.find_by_id(1)
293 end
294 end
294 end
295 end
295
296
296 context "DELETE /issues/1.json" do
297 context "DELETE /issues/1.json" do
297 setup do
298 setup do
298 @issue_count = Issue.count
299 @issue_count = Issue.count
299 delete '/issues/1.json', {}, :authorization => credentials('jsmith')
300 delete '/issues/1.json', {}, :authorization => credentials('jsmith')
300 end
301 end
301
302
302 should_respond_with :ok
303 should_respond_with :ok
303 should_respond_with_content_type 'application/json'
304 should_respond_with_content_type 'application/json'
304
305
305 should "delete the issue" do
306 should "delete the issue" do
306 assert_equal Issue.count, @issue_count -1
307 assert_equal Issue.count, @issue_count -1
307 assert_nil Issue.find_by_id(1)
308 assert_nil Issue.find_by_id(1)
308 end
309 end
309 end
310 end
310
311
311 def credentials(user, password=nil)
312 def credentials(user, password=nil)
312 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
313 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
313 end
314 end
314 end
315 end
@@ -1,393 +1,420
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 ENV["RAILS_ENV"] = "test"
18 ENV["RAILS_ENV"] = "test"
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require 'test_help'
20 require 'test_help'
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
22 require File.join(RAILS_ROOT,'test', 'mocks', 'open_id_authentication_mock.rb')
22 require File.join(RAILS_ROOT,'test', 'mocks', 'open_id_authentication_mock.rb')
23
23
24 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
24 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
25 include ObjectDaddyHelpers
25 include ObjectDaddyHelpers
26
26
27 class ActiveSupport::TestCase
27 class ActiveSupport::TestCase
28 # Transactional fixtures accelerate your tests by wrapping each test method
28 # Transactional fixtures accelerate your tests by wrapping each test method
29 # in a transaction that's rolled back on completion. This ensures that the
29 # in a transaction that's rolled back on completion. This ensures that the
30 # test database remains unchanged so your fixtures don't have to be reloaded
30 # test database remains unchanged so your fixtures don't have to be reloaded
31 # between every test method. Fewer database queries means faster tests.
31 # between every test method. Fewer database queries means faster tests.
32 #
32 #
33 # Read Mike Clark's excellent walkthrough at
33 # Read Mike Clark's excellent walkthrough at
34 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
34 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
35 #
35 #
36 # Every Active Record database supports transactions except MyISAM tables
36 # Every Active Record database supports transactions except MyISAM tables
37 # in MySQL. Turn off transactional fixtures in this case; however, if you
37 # in MySQL. Turn off transactional fixtures in this case; however, if you
38 # don't care one way or the other, switching from MyISAM to InnoDB tables
38 # don't care one way or the other, switching from MyISAM to InnoDB tables
39 # is recommended.
39 # is recommended.
40 self.use_transactional_fixtures = true
40 self.use_transactional_fixtures = true
41
41
42 # Instantiated fixtures are slow, but give you @david where otherwise you
42 # Instantiated fixtures are slow, but give you @david where otherwise you
43 # would need people(:david). If you don't want to migrate your existing
43 # would need people(:david). If you don't want to migrate your existing
44 # test cases which use the @david style and don't mind the speed hit (each
44 # test cases which use the @david style and don't mind the speed hit (each
45 # instantiated fixtures translates to a database query per test method),
45 # instantiated fixtures translates to a database query per test method),
46 # then set this back to true.
46 # then set this back to true.
47 self.use_instantiated_fixtures = false
47 self.use_instantiated_fixtures = false
48
48
49 # Add more helper methods to be used by all tests here...
49 # Add more helper methods to be used by all tests here...
50
50
51 def log_user(login, password)
51 def log_user(login, password)
52 User.anonymous
52 User.anonymous
53 get "/login"
53 get "/login"
54 assert_equal nil, session[:user_id]
54 assert_equal nil, session[:user_id]
55 assert_response :success
55 assert_response :success
56 assert_template "account/login"
56 assert_template "account/login"
57 post "/login", :username => login, :password => password
57 post "/login", :username => login, :password => password
58 assert_equal login, User.find(session[:user_id]).login
58 assert_equal login, User.find(session[:user_id]).login
59 end
59 end
60
60
61 def uploaded_test_file(name, mime)
61 def uploaded_test_file(name, mime)
62 ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime)
62 ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime)
63 end
63 end
64
64
65 # Mock out a file
65 # Mock out a file
66 def self.mock_file
66 def self.mock_file
67 file = 'a_file.png'
67 file = 'a_file.png'
68 file.stubs(:size).returns(32)
68 file.stubs(:size).returns(32)
69 file.stubs(:original_filename).returns('a_file.png')
69 file.stubs(:original_filename).returns('a_file.png')
70 file.stubs(:content_type).returns('image/png')
70 file.stubs(:content_type).returns('image/png')
71 file.stubs(:read).returns(false)
71 file.stubs(:read).returns(false)
72 file
72 file
73 end
73 end
74
74
75 def mock_file
75 def mock_file
76 self.class.mock_file
76 self.class.mock_file
77 end
77 end
78
78
79 # Use a temporary directory for attachment related tests
79 # Use a temporary directory for attachment related tests
80 def set_tmp_attachments_directory
80 def set_tmp_attachments_directory
81 Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
81 Dir.mkdir "#{RAILS_ROOT}/tmp/test" unless File.directory?("#{RAILS_ROOT}/tmp/test")
82 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
82 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
83 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
83 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
84 end
84 end
85
85
86 def with_settings(options, &block)
86 def with_settings(options, &block)
87 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].dup; h}
87 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].dup; h}
88 options.each {|k, v| Setting[k] = v}
88 options.each {|k, v| Setting[k] = v}
89 yield
89 yield
90 saved_settings.each {|k, v| Setting[k] = v}
90 saved_settings.each {|k, v| Setting[k] = v}
91 end
91 end
92
92
93 def change_user_password(login, new_password)
93 def change_user_password(login, new_password)
94 user = User.first(:conditions => {:login => login})
94 user = User.first(:conditions => {:login => login})
95 user.password, user.password_confirmation = new_password, new_password
95 user.password, user.password_confirmation = new_password, new_password
96 user.save!
96 user.save!
97 end
97 end
98
98
99 def self.ldap_configured?
99 def self.ldap_configured?
100 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
100 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
101 return @test_ldap.bind
101 return @test_ldap.bind
102 rescue Exception => e
102 rescue Exception => e
103 # LDAP is not listening
103 # LDAP is not listening
104 return nil
104 return nil
105 end
105 end
106
106
107 # Returns the path to the test +vendor+ repository
107 # Returns the path to the test +vendor+ repository
108 def self.repository_path(vendor)
108 def self.repository_path(vendor)
109 File.join(RAILS_ROOT.gsub(%r{config\/\.\.}, ''), "/tmp/test/#{vendor.downcase}_repository")
109 File.join(RAILS_ROOT.gsub(%r{config\/\.\.}, ''), "/tmp/test/#{vendor.downcase}_repository")
110 end
110 end
111
111
112 # Returns true if the +vendor+ test repository is configured
112 # Returns true if the +vendor+ test repository is configured
113 def self.repository_configured?(vendor)
113 def self.repository_configured?(vendor)
114 File.directory?(repository_path(vendor))
114 File.directory?(repository_path(vendor))
115 end
115 end
116
116
117 def assert_error_tag(options={})
117 def assert_error_tag(options={})
118 assert_tag({:tag => 'p', :attributes => { :id => 'errorExplanation' }}.merge(options))
118 assert_tag({:tag => 'p', :attributes => { :id => 'errorExplanation' }}.merge(options))
119 end
119 end
120
120
121 # Shoulda macros
121 # Shoulda macros
122 def self.should_render_404
122 def self.should_render_404
123 should_respond_with :not_found
123 should_respond_with :not_found
124 should_render_template 'common/error'
124 should_render_template 'common/error'
125 end
125 end
126
126
127 def self.should_have_before_filter(expected_method, options = {})
127 def self.should_have_before_filter(expected_method, options = {})
128 should_have_filter('before', expected_method, options)
128 should_have_filter('before', expected_method, options)
129 end
129 end
130
130
131 def self.should_have_after_filter(expected_method, options = {})
131 def self.should_have_after_filter(expected_method, options = {})
132 should_have_filter('after', expected_method, options)
132 should_have_filter('after', expected_method, options)
133 end
133 end
134
134
135 def self.should_have_filter(filter_type, expected_method, options)
135 def self.should_have_filter(filter_type, expected_method, options)
136 description = "have #{filter_type}_filter :#{expected_method}"
136 description = "have #{filter_type}_filter :#{expected_method}"
137 description << " with #{options.inspect}" unless options.empty?
137 description << " with #{options.inspect}" unless options.empty?
138
138
139 should description do
139 should description do
140 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
140 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
141 expected = klass.new(:filter, expected_method.to_sym, options)
141 expected = klass.new(:filter, expected_method.to_sym, options)
142 assert_equal 1, @controller.class.filter_chain.select { |filter|
142 assert_equal 1, @controller.class.filter_chain.select { |filter|
143 filter.method == expected.method && filter.kind == expected.kind &&
143 filter.method == expected.method && filter.kind == expected.kind &&
144 filter.options == expected.options && filter.class == expected.class
144 filter.options == expected.options && filter.class == expected.class
145 }.size
145 }.size
146 end
146 end
147 end
147 end
148
148
149 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
149 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
150 context "" do
150 context "" do
151 setup do
151 setup do
152 if block_given?
152 if block_given?
153 instance_eval &block
153 instance_eval &block
154 else
154 else
155 @old_value = model.generate!
155 @old_value = model.generate!
156 @new_value = model.generate!
156 @new_value = model.generate!
157 end
157 end
158 end
158 end
159
159
160 should "use the new value's name" do
160 should "use the new value's name" do
161 @detail = JournalDetail.generate!(:property => 'attr',
161 @detail = JournalDetail.generate!(:property => 'attr',
162 :old_value => @old_value.id,
162 :old_value => @old_value.id,
163 :value => @new_value.id,
163 :value => @new_value.id,
164 :prop_key => prop_key)
164 :prop_key => prop_key)
165
165
166 assert_match @new_value.name, show_detail(@detail, true)
166 assert_match @new_value.name, show_detail(@detail, true)
167 end
167 end
168
168
169 should "use the old value's name" do
169 should "use the old value's name" do
170 @detail = JournalDetail.generate!(:property => 'attr',
170 @detail = JournalDetail.generate!(:property => 'attr',
171 :old_value => @old_value.id,
171 :old_value => @old_value.id,
172 :value => @new_value.id,
172 :value => @new_value.id,
173 :prop_key => prop_key)
173 :prop_key => prop_key)
174
174
175 assert_match @old_value.name, show_detail(@detail, true)
175 assert_match @old_value.name, show_detail(@detail, true)
176 end
176 end
177 end
177 end
178 end
178 end
179
179
180 def self.should_create_a_new_user(&block)
180 def self.should_create_a_new_user(&block)
181 should "create a new user" do
181 should "create a new user" do
182 user = instance_eval &block
182 user = instance_eval &block
183 assert user
183 assert user
184 assert_kind_of User, user
184 assert_kind_of User, user
185 assert !user.new_record?
185 assert !user.new_record?
186 end
186 end
187 end
187 end
188
188
189 # Test that a request allows the three types of API authentication
189 # Test that a request allows the three types of API authentication
190 #
190 #
191 # * HTTP Basic with username and password
191 # * HTTP Basic with username and password
192 # * HTTP Basic with an api key for the username
192 # * HTTP Basic with an api key for the username
193 # * Key based with the key=X parameter
193 # * Key based with the key=X parameter
194 #
194 #
195 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
195 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
196 # @param [String] url the request url
196 # @param [String] url the request url
197 # @param [optional, Hash] parameters additional request parameters
197 # @param [optional, Hash] parameters additional request parameters
198 def self.should_allow_api_authentication(http_method, url, parameters={})
198 # @param [optional, Hash] options additional options
199 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters)
199 # @option options [Symbol] :success_code Successful response code (:success)
200 should_allow_http_basic_auth_with_key(http_method, url, parameters)
200 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
201 should_allow_key_based_auth(http_method, url, parameters)
201 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
202 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
203 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
204 should_allow_key_based_auth(http_method, url, parameters, options)
202 end
205 end
203
206
204 # Test that a request allows the username and password for HTTP BASIC
207 # Test that a request allows the username and password for HTTP BASIC
205 #
208 #
206 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
209 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
207 # @param [String] url the request url
210 # @param [String] url the request url
208 # @param [optional, Hash] parameters additional request parameters
211 # @param [optional, Hash] parameters additional request parameters
209 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={})
212 # @param [optional, Hash] options additional options
213 # @option options [Symbol] :success_code Successful response code (:success)
214 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
215 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
216 success_code = options[:success_code] || :success
217 failure_code = options[:failure_code] || :unauthorized
218
210 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
219 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
211 context "with a valid HTTP authentication" do
220 context "with a valid HTTP authentication" do
212 setup do
221 setup do
213 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
222 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
214 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
223 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
215 send(http_method, url, parameters, {:authorization => @authorization})
224 send(http_method, url, parameters, {:authorization => @authorization})
216 end
225 end
217
226
218 should_respond_with :success
227 should_respond_with success_code
219 should_respond_with_content_type_based_on_url(url)
228 should_respond_with_content_type_based_on_url(url)
220 should "login as the user" do
229 should "login as the user" do
221 assert_equal @user, User.current
230 assert_equal @user, User.current
222 end
231 end
223 end
232 end
224
233
225 context "with an invalid HTTP authentication" do
234 context "with an invalid HTTP authentication" do
226 setup do
235 setup do
227 @user = User.generate_with_protected!
236 @user = User.generate_with_protected!
228 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
237 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
229 send(http_method, url, parameters, {:authorization => @authorization})
238 send(http_method, url, parameters, {:authorization => @authorization})
230 end
239 end
231
240
232 should_respond_with :unauthorized
241 should_respond_with failure_code
233 should_respond_with_content_type_based_on_url(url)
242 should_respond_with_content_type_based_on_url(url)
234 should "not login as the user" do
243 should "not login as the user" do
235 assert_equal User.anonymous, User.current
244 assert_equal User.anonymous, User.current
236 end
245 end
237 end
246 end
238
247
239 context "without credentials" do
248 context "without credentials" do
240 setup do
249 setup do
241 send(http_method, url, parameters, {:authorization => ''})
250 send(http_method, url, parameters, {:authorization => ''})
242 end
251 end
243
252
244 should_respond_with :unauthorized
253 should_respond_with failure_code
245 should_respond_with_content_type_based_on_url(url)
254 should_respond_with_content_type_based_on_url(url)
246 should "include_www_authenticate_header" do
255 should "include_www_authenticate_header" do
247 assert @controller.response.headers.has_key?('WWW-Authenticate')
256 assert @controller.response.headers.has_key?('WWW-Authenticate')
248 end
257 end
249 end
258 end
250 end
259 end
251
260
252 end
261 end
253
262
254 # Test that a request allows the API key with HTTP BASIC
263 # Test that a request allows the API key with HTTP BASIC
255 #
264 #
256 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
265 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
257 # @param [String] url the request url
266 # @param [String] url the request url
258 # @param [optional, Hash] parameters additional request parameters
267 # @param [optional, Hash] parameters additional request parameters
259 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={})
268 # @param [optional, Hash] options additional options
269 # @option options [Symbol] :success_code Successful response code (:success)
270 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
271 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
272 success_code = options[:success_code] || :success
273 failure_code = options[:failure_code] || :unauthorized
274
260 context "should allow http basic auth with a key for #{http_method} #{url}" do
275 context "should allow http basic auth with a key for #{http_method} #{url}" do
261 context "with a valid HTTP authentication using the API token" do
276 context "with a valid HTTP authentication using the API token" do
262 setup do
277 setup do
263 @user = User.generate_with_protected!(:admin => true)
278 @user = User.generate_with_protected!(:admin => true)
264 @token = Token.generate!(:user => @user, :action => 'api')
279 @token = Token.generate!(:user => @user, :action => 'api')
265 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
280 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
266 send(http_method, url, parameters, {:authorization => @authorization})
281 send(http_method, url, parameters, {:authorization => @authorization})
267 end
282 end
268
283
269 should_respond_with :success
284 should_respond_with success_code
270 should_respond_with_content_type_based_on_url(url)
285 should_respond_with_content_type_based_on_url(url)
271 should_be_a_valid_response_string_based_on_url(url)
286 should_be_a_valid_response_string_based_on_url(url)
272 should "login as the user" do
287 should "login as the user" do
273 assert_equal @user, User.current
288 assert_equal @user, User.current
274 end
289 end
275 end
290 end
276
291
277 context "with an invalid HTTP authentication" do
292 context "with an invalid HTTP authentication" do
278 setup do
293 setup do
279 @user = User.generate_with_protected!
294 @user = User.generate_with_protected!
280 @token = Token.generate!(:user => @user, :action => 'feeds')
295 @token = Token.generate!(:user => @user, :action => 'feeds')
281 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
296 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
282 send(http_method, url, parameters, {:authorization => @authorization})
297 send(http_method, url, parameters, {:authorization => @authorization})
283 end
298 end
284
299
285 should_respond_with :unauthorized
300 should_respond_with failure_code
286 should_respond_with_content_type_based_on_url(url)
301 should_respond_with_content_type_based_on_url(url)
287 should "not login as the user" do
302 should "not login as the user" do
288 assert_equal User.anonymous, User.current
303 assert_equal User.anonymous, User.current
289 end
304 end
290 end
305 end
291 end
306 end
292 end
307 end
293
308
294 # Test that a request allows full key authentication
309 # Test that a request allows full key authentication
295 #
310 #
296 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
311 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
297 # @param [String] url the request url, without the key=ZXY parameter
312 # @param [String] url the request url, without the key=ZXY parameter
298 # @param [optional, Hash] parameters additional request parameters
313 # @param [optional, Hash] parameters additional request parameters
299 def self.should_allow_key_based_auth(http_method, url, parameters={})
314 # @param [optional, Hash] options additional options
315 # @option options [Symbol] :success_code Successful response code (:success)
316 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
317 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
318 success_code = options[:success_code] || :success
319 failure_code = options[:failure_code] || :unauthorized
320
300 context "should allow key based auth using key=X for #{http_method} #{url}" do
321 context "should allow key based auth using key=X for #{http_method} #{url}" do
301 context "with a valid api token" do
322 context "with a valid api token" do
302 setup do
323 setup do
303 @user = User.generate_with_protected!(:admin => true)
324 @user = User.generate_with_protected!(:admin => true)
304 @token = Token.generate!(:user => @user, :action => 'api')
325 @token = Token.generate!(:user => @user, :action => 'api')
305 # Simple url parse to add on ?key= or &key=
326 # Simple url parse to add on ?key= or &key=
306 request_url = if url.match(/\?/)
327 request_url = if url.match(/\?/)
307 url + "&key=#{@token.value}"
328 url + "&key=#{@token.value}"
308 else
329 else
309 url + "?key=#{@token.value}"
330 url + "?key=#{@token.value}"
310 end
331 end
311 send(http_method, request_url, parameters)
332 send(http_method, request_url, parameters)
312 end
333 end
313
334
314 should_respond_with :success
335 should_respond_with success_code
315 should_respond_with_content_type_based_on_url(url)
336 should_respond_with_content_type_based_on_url(url)
316 should_be_a_valid_response_string_based_on_url(url)
337 should_be_a_valid_response_string_based_on_url(url)
317 should "login as the user" do
338 should "login as the user" do
318 assert_equal @user, User.current
339 assert_equal @user, User.current
319 end
340 end
320 end
341 end
321
342
322 context "with an invalid api token" do
343 context "with an invalid api token" do
323 setup do
344 setup do
324 @user = User.generate_with_protected!
345 @user = User.generate_with_protected!
325 @token = Token.generate!(:user => @user, :action => 'feeds')
346 @token = Token.generate!(:user => @user, :action => 'feeds')
326 send(http_method, url + "?key=#{@token.value}")
347 # Simple url parse to add on ?key= or &key=
348 request_url = if url.match(/\?/)
349 url + "&key=#{@token.value}"
350 else
351 url + "?key=#{@token.value}"
352 end
353 send(http_method, request_url, parameters)
327 end
354 end
328
355
329 should_respond_with :unauthorized
356 should_respond_with failure_code
330 should_respond_with_content_type_based_on_url(url)
357 should_respond_with_content_type_based_on_url(url)
331 should "not login as the user" do
358 should "not login as the user" do
332 assert_equal User.anonymous, User.current
359 assert_equal User.anonymous, User.current
333 end
360 end
334 end
361 end
335 end
362 end
336
363
337 end
364 end
338
365
339 # Uses should_respond_with_content_type based on what's in the url:
366 # Uses should_respond_with_content_type based on what's in the url:
340 #
367 #
341 # '/project/issues.xml' => should_respond_with_content_type :xml
368 # '/project/issues.xml' => should_respond_with_content_type :xml
342 # '/project/issues.json' => should_respond_with_content_type :json
369 # '/project/issues.json' => should_respond_with_content_type :json
343 #
370 #
344 # @param [String] url Request
371 # @param [String] url Request
345 def self.should_respond_with_content_type_based_on_url(url)
372 def self.should_respond_with_content_type_based_on_url(url)
346 case
373 case
347 when url.match(/xml/i)
374 when url.match(/xml/i)
348 should_respond_with_content_type :xml
375 should_respond_with_content_type :xml
349 when url.match(/json/i)
376 when url.match(/json/i)
350 should_respond_with_content_type :json
377 should_respond_with_content_type :json
351 else
378 else
352 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
379 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
353 end
380 end
354
381
355 end
382 end
356
383
357 # Uses the url to assert which format the response should be in
384 # Uses the url to assert which format the response should be in
358 #
385 #
359 # '/project/issues.xml' => should_be_a_valid_xml_string
386 # '/project/issues.xml' => should_be_a_valid_xml_string
360 # '/project/issues.json' => should_be_a_valid_json_string
387 # '/project/issues.json' => should_be_a_valid_json_string
361 #
388 #
362 # @param [String] url Request
389 # @param [String] url Request
363 def self.should_be_a_valid_response_string_based_on_url(url)
390 def self.should_be_a_valid_response_string_based_on_url(url)
364 case
391 case
365 when url.match(/xml/i)
392 when url.match(/xml/i)
366 should_be_a_valid_xml_string
393 should_be_a_valid_xml_string
367 when url.match(/json/i)
394 when url.match(/json/i)
368 should_be_a_valid_json_string
395 should_be_a_valid_json_string
369 else
396 else
370 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
397 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
371 end
398 end
372
399
373 end
400 end
374
401
375 # Checks that the response is a valid JSON string
402 # Checks that the response is a valid JSON string
376 def self.should_be_a_valid_json_string
403 def self.should_be_a_valid_json_string
377 should "be a valid JSON string" do
404 should "be a valid JSON string" do
378 assert ActiveSupport::JSON.decode(response.body)
405 assert ActiveSupport::JSON.decode(response.body)
379 end
406 end
380 end
407 end
381
408
382 # Checks that the response is a valid XML string
409 # Checks that the response is a valid XML string
383 def self.should_be_a_valid_xml_string
410 def self.should_be_a_valid_xml_string
384 should "be a valid XML string" do
411 should "be a valid XML string" do
385 assert REXML::Document.new(response.body)
412 assert REXML::Document.new(response.body)
386 end
413 end
387 end
414 end
388
415
389 end
416 end
390
417
391 # Simple module to "namespace" all of the API tests
418 # Simple module to "namespace" all of the API tests
392 module ApiTest
419 module ApiTest
393 end
420 end
General Comments 0
You need to be logged in to leave comments. Login now