##// END OF EJS Templates
Converts IssuesController to use the new API template system and makes xml/json responses consistent (#6136)....
Jean-Philippe Lang -
r4344:735a83c59615
parent child
Show More
@@ -0,0 +1,32
1 api.array :issues do
2 @issues.each do |issue|
3 api.issue do
4 api.id issue.id
5 api.project(:id => issue.project_id, :name => issue.project.name) unless issue.project.nil?
6 api.tracker(:id => issue.tracker_id, :name => issue.tracker.name) unless issue.tracker.nil?
7 api.status(:id => issue.status_id, :name => issue.status.name) unless issue.status.nil?
8 api.priority(:id => issue.priority_id, :name => issue.priority.name) unless issue.priority.nil?
9 api.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil?
10 api.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil?
11 api.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil?
12 api.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil?
13 api.parent(:id => issue.parent_id) unless issue.parent.nil?
14
15 api.subject issue.subject
16 api.description issue.description
17 api.start_date issue.start_date
18 api.due_date issue.due_date
19 api.done_ratio issue.done_ratio
20 api.estimated_hours issue.estimated_hours
21
22 api.array :custom_fields do
23 issue.custom_field_values.each do |custom_value|
24 api.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
25 end
26 end
27
28 api.created_on issue.created_on
29 api.updated_on issue.updated_on
30 end
31 end
32 end
@@ -0,0 +1,61
1 api.issue do
2 api.id @issue.id
3 api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil?
4 api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil?
5 api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil?
6 api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil?
7 api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil?
8 api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil?
9 api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil?
10 api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil?
11 api.parent(:id => @issue.parent_id) unless @issue.parent.nil?
12
13 api.subject @issue.subject
14 api.description @issue.description
15 api.start_date @issue.start_date
16 api.due_date @issue.due_date
17 api.done_ratio @issue.done_ratio
18 api.estimated_hours @issue.estimated_hours
19 if User.current.allowed_to?(:view_time_entries, @project)
20 api.spent_hours @issue.spent_hours
21 end
22
23 api.array :custom_fields do
24 @issue.custom_field_values.each do |custom_value|
25 api.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
26 end
27 end unless @issue.custom_field_values.empty?
28
29 api.created_on @issue.created_on
30 api.updated_on @issue.updated_on
31
32 api.array :relations do
33 @issue.relations.select {|r| r.other_issue(@issue).visible? }.each do |relation|
34 api.relation(:id => relation.id, :issue_id => relation.other_issue(@issue).id, :relation_type => relation.relation_type_for(@issue), :delay => relation.delay)
35 end
36 end
37
38 api.array :changesets do
39 @issue.changesets.each do |changeset|
40 api.changeset :revision => changeset.revision do
41 api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil?
42 api.comments changeset.comments
43 api.committed_on changeset.committed_on
44 end
45 end
46 end if User.current.allowed_to?(:view_changesets, @project) && @issue.changesets.any?
47
48 api.array :journals do
49 @issue.journals.each do |journal|
50 api.journal :id => journal.id do
51 api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
52 api.notes journal.notes
53 api.array :details do
54 journal.details.each do |detail|
55 api.detail :property => detail.property, :name => detail.prop_key, :old => detail.old_value, :new => detail.value
56 end
57 end
58 end
59 end
60 end unless @issue.journals.empty?
61 end
@@ -1,322 +1,313
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssuesController < ApplicationController
19 19 menu_item :new_issue, :only => [:new, :create]
20 20 default_search_scope :issues
21 21
22 22 before_filter :find_issue, :only => [:show, :edit, :update]
23 23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
25 25 before_filter :find_project, :only => [:new, :create]
26 26 before_filter :authorize, :except => [:index]
27 27 before_filter :find_optional_project, :only => [:index]
28 28 before_filter :check_for_default_issue_status, :only => [:new, :create]
29 29 before_filter :build_new_issue_from_params, :only => [:new, :create]
30 30 accept_key_auth :index, :show, :create, :update, :destroy
31 31
32 32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33 33
34 34 helper :journals
35 35 helper :projects
36 36 include ProjectsHelper
37 37 helper :custom_fields
38 38 include CustomFieldsHelper
39 39 helper :issue_relations
40 40 include IssueRelationsHelper
41 41 helper :watchers
42 42 include WatchersHelper
43 43 helper :attachments
44 44 include AttachmentsHelper
45 45 helper :queries
46 46 include QueriesHelper
47 47 helper :sort
48 48 include SortHelper
49 49 include IssuesHelper
50 50 helper :timelog
51 51 helper :gantt
52 52 include Redmine::Export::PDF
53 53
54 54 verify :method => [:post, :delete],
55 55 :only => :destroy,
56 56 :render => { :nothing => true, :status => :method_not_allowed }
57 57
58 58 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
59 59 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
60 60 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
61 61
62 62 def index
63 63 retrieve_query
64 64 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
65 65 sort_update(@query.sortable_columns)
66 66
67 67 if @query.valid?
68 68 limit = case params[:format]
69 69 when 'csv', 'pdf'
70 70 Setting.issues_export_limit.to_i
71 71 when 'atom'
72 72 Setting.feeds_limit.to_i
73 73 else
74 74 per_page_option
75 75 end
76 76
77 77 @issue_count = @query.issue_count
78 78 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
79 79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
80 80 :order => sort_clause,
81 81 :offset => @issue_pages.current.offset,
82 82 :limit => limit)
83 83 @issue_count_by_group = @query.issue_count_by_group
84 84
85 85 respond_to do |format|
86 86 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
87 format.xml { render :layout => false }
88 format.json { render :text => @issues.to_json, :layout => false }
87 format.api { render :template => 'issues/index.apit' }
89 88 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
90 89 format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') }
91 90 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
92 91 end
93 92 else
94 93 # Send html if the query is not valid
95 94 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
96 95 end
97 96 rescue ActiveRecord::RecordNotFound
98 97 render_404
99 98 end
100 99
101 100 def show
102 101 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
103 102 @journals.each_with_index {|j,i| j.indice = i+1}
104 103 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105 104 @changesets = @issue.changesets.visible.all
106 105 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107 106 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
108 107 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
109 108 @priorities = IssuePriority.all
110 109 @time_entry = TimeEntry.new
111 110 respond_to do |format|
112 111 format.html { render :template => 'issues/show.rhtml' }
113 format.xml { render :layout => false }
114 format.json { render :text => @issue.to_json, :layout => false }
112 format.api { render :template => 'issues/show.apit' }
115 113 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
116 114 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
117 115 end
118 116 end
119 117
120 118 # Add a new issue
121 119 # The new issue will be created from an existing one if copy_from parameter is given
122 120 def new
123 121 respond_to do |format|
124 122 format.html { render :action => 'new', :layout => !request.xhr? }
125 123 format.js { render :partial => 'attributes' }
126 124 end
127 125 end
128 126
129 127 def create
130 128 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
131 129 if @issue.save
132 130 attachments = Attachment.attach_files(@issue, params[:attachments])
133 131 render_attachment_warning_if_needed(@issue)
134 132 flash[:notice] = l(:notice_successful_create)
135 133 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
136 134 respond_to do |format|
137 135 format.html {
138 136 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 137 { :action => 'show', :id => @issue })
140 138 }
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 }
139 format.api { render :template => 'issues/show.apit', :status => :created, :location => issue_url(@issue) }
143 140 end
144 141 return
145 142 else
146 143 respond_to do |format|
147 144 format.html { render :action => 'new' }
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 }
145 format.api { render_validation_errors(@issue) }
150 146 end
151 147 end
152 148 end
153 149
154 150 def edit
155 151 update_issue_from_params
156 152
157 153 @journal = @issue.current_journal
158 154
159 155 respond_to do |format|
160 156 format.html { }
161 157 format.xml { }
162 158 end
163 159 end
164 160
165 161 def update
166 162 update_issue_from_params
167 163
168 164 if @issue.save_issue_with_child_records(params, @time_entry)
169 165 render_attachment_warning_if_needed(@issue)
170 166 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
171 167
172 168 respond_to do |format|
173 169 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
174 format.xml { head :ok }
175 format.json { head :ok }
170 format.api { head :ok }
176 171 end
177 172 else
178 173 render_attachment_warning_if_needed(@issue)
179 174 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
180 175 @journal = @issue.current_journal
181 176
182 177 respond_to do |format|
183 178 format.html { render :action => 'edit' }
184 format.xml { render :xml => @issue.errors, :status => :unprocessable_entity }
185 format.json { render :text => object_errors_to_json(@issue), :status => :unprocessable_entity, :layout => false }
179 format.api { render_validation_errors(@issue) }
186 180 end
187 181 end
188 182 end
189 183
190 184 # Bulk edit a set of issues
191 185 def bulk_edit
192 186 @issues.sort!
193 187 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
194 188 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
195 189 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
196 190 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
197 191 end
198 192
199 193 def bulk_update
200 194 @issues.sort!
201 195 attributes = parse_params_for_bulk_issue_attributes(params)
202 196
203 197 unsaved_issue_ids = []
204 198 @issues.each do |issue|
205 199 issue.reload
206 200 journal = issue.init_journal(User.current, params[:notes])
207 201 issue.safe_attributes = attributes
208 202 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
209 203 unless issue.save
210 204 # Keep unsaved issue ids to display them in flash error
211 205 unsaved_issue_ids << issue.id
212 206 end
213 207 end
214 208 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
215 209 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
216 210 end
217 211
218 212 def destroy
219 213 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
220 214 if @hours > 0
221 215 case params[:todo]
222 216 when 'destroy'
223 217 # nothing to do
224 218 when 'nullify'
225 219 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
226 220 when 'reassign'
227 221 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
228 222 if reassign_to.nil?
229 223 flash.now[:error] = l(:error_issue_not_found_in_project)
230 224 return
231 225 else
232 226 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
233 227 end
234 228 else
235 unless params[:format] == 'xml' || params[:format] == 'json'
236 # display the destroy form if it's a user request
237 return
238 end
229 # display the destroy form if it's a user request
230 return unless api_request?
239 231 end
240 232 end
241 233 @issues.each(&:destroy)
242 234 respond_to do |format|
243 235 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
244 format.xml { head :ok }
245 format.json { head :ok }
236 format.api { head :ok }
246 237 end
247 238 end
248 239
249 240 private
250 241 def find_issue
251 242 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
252 243 @project = @issue.project
253 244 rescue ActiveRecord::RecordNotFound
254 245 render_404
255 246 end
256 247
257 248 def find_project
258 249 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
259 250 @project = Project.find(project_id)
260 251 rescue ActiveRecord::RecordNotFound
261 252 render_404
262 253 end
263 254
264 255 # Used by #edit and #update to set some common instance variables
265 256 # from the params
266 257 # TODO: Refactor, not everything in here is needed by #edit
267 258 def update_issue_from_params
268 259 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
269 260 @priorities = IssuePriority.all
270 261 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
271 262 @time_entry = TimeEntry.new
272 263 @time_entry.attributes = params[:time_entry]
273 264
274 265 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
275 266 @issue.init_journal(User.current, @notes)
276 267 @issue.safe_attributes = params[:issue]
277 268 end
278 269
279 270 # TODO: Refactor, lots of extra code in here
280 271 # TODO: Changing tracker on an existing issue should not trigger this
281 272 def build_new_issue_from_params
282 273 if params[:id].blank?
283 274 @issue = Issue.new
284 275 @issue.copy_from(params[:copy_from]) if params[:copy_from]
285 276 @issue.project = @project
286 277 else
287 278 @issue = @project.issues.visible.find(params[:id])
288 279 end
289 280
290 281 @issue.project = @project
291 282 # Tracker must be set before custom field values
292 283 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
293 284 if @issue.tracker.nil?
294 285 render_error l(:error_no_tracker_in_project)
295 286 return false
296 287 end
297 288 @issue.start_date ||= Date.today
298 289 if params[:issue].is_a?(Hash)
299 290 @issue.safe_attributes = params[:issue]
300 291 if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
301 292 @issue.watcher_user_ids = params[:issue]['watcher_user_ids']
302 293 end
303 294 end
304 295 @issue.author = User.current
305 296 @priorities = IssuePriority.all
306 297 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
307 298 end
308 299
309 300 def check_for_default_issue_status
310 301 if IssueStatus.default.nil?
311 302 render_error l(:error_no_default_issue_status)
312 303 return false
313 304 end
314 305 end
315 306
316 307 def parse_params_for_bulk_issue_attributes(params)
317 308 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
318 309 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
319 310 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
320 311 attributes
321 312 end
322 313 end
@@ -1,68 +1,70
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'blankslate'
19 19
20 20 module Redmine
21 21 module Views
22 22 module Builders
23 23 class Structure < BlankSlate
24 24 def initialize
25 25 @struct = [{}]
26 26 end
27 27
28 28 def array(tag, &block)
29 29 @struct << []
30 30 block.call(self)
31 31 ret = @struct.pop
32 32 @struct.last[tag] = ret
33 33 end
34 34
35 35 def method_missing(sym, *args, &block)
36 36 if args.any?
37 37 if args.first.is_a?(Hash)
38 38 if @struct.last.is_a?(Array)
39 39 @struct.last << args.first
40 else
41 @struct.last[sym] = args.first
40 42 end
41 43 else
42 44 if @struct.last.is_a?(Array)
43 45 @struct.last << (args.last || {}).merge(:value => args.first)
44 46 else
45 47 @struct.last[sym] = args.first
46 48 end
47 49 end
48 50 end
49 51
50 52 if block
51 53 @struct << {}
52 54 block.call(self)
53 55 ret = @struct.pop
54 56 if @struct.last.is_a?(Array)
55 57 @struct.last << ret
56 58 else
57 59 @struct.last[sym] = ret
58 60 end
59 61 end
60 62 end
61 63
62 64 def output
63 65 raise "Need to implement #{self.class.name}#output"
64 66 end
65 67 end
66 68 end
67 69 end
68 70 end
@@ -1,340 +1,340
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "#{File.dirname(__FILE__)}/../../test_helper"
19 19
20 20 class ApiTest::IssuesTest < ActionController::IntegrationTest
21 21 fixtures :projects,
22 22 :users,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
26 26 :issues,
27 27 :issue_statuses,
28 28 :versions,
29 29 :trackers,
30 30 :projects_trackers,
31 31 :issue_categories,
32 32 :enabled_modules,
33 33 :enumerations,
34 34 :attachments,
35 35 :workflows,
36 36 :custom_fields,
37 37 :custom_values,
38 38 :custom_fields_projects,
39 39 :custom_fields_trackers,
40 40 :time_entries,
41 41 :journals,
42 42 :journal_details,
43 43 :queries
44 44
45 45 def setup
46 46 Setting.rest_api_enabled = '1'
47 47 end
48 48
49 49 # Use a private project to make sure auth is really working and not just
50 50 # only showing public issues.
51 51 context "/index.xml" do
52 52 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
53 53 end
54 54
55 55 context "/index.json" do
56 56 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
57 57 end
58 58
59 59 context "/index.xml with filter" do
60 60 should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
61 61
62 62 should "show only issues with the status_id" do
63 63 get '/issues.xml?status_id=5'
64 64 assert_tag :tag => 'issues',
65 65 :children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
66 66 :only => { :tag => 'issue' } }
67 67 end
68 68 end
69 69
70 70 context "/index.json with filter" do
71 71 should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
72 72
73 73 should "show only issues with the status_id" do
74 74 get '/issues.json?status_id=5'
75 75
76 76 json = ActiveSupport::JSON.decode(response.body)
77 status_ids_used = json.collect {|j| j['status_id'] }
77 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
78 78 assert_equal 3, status_ids_used.length
79 79 assert status_ids_used.all? {|id| id == 5 }
80 80 end
81 81
82 82 end
83 83
84 84 # Issue 6 is on a private project
85 85 context "/issues/6.xml" do
86 86 should_allow_api_authentication(:get, "/issues/6.xml")
87 87 end
88 88
89 89 context "/issues/6.json" do
90 90 should_allow_api_authentication(:get, "/issues/6.json")
91 91 end
92 92
93 93 context "POST /issues.xml" do
94 94 should_allow_api_authentication(:post,
95 95 '/issues.xml',
96 96 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
97 97 {:success_code => :created})
98 98
99 99 should "create an issue with the attributes" do
100 100 assert_difference('Issue.count') do
101 101 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
102 102 end
103 103
104 104 issue = Issue.first(:order => 'id DESC')
105 105 assert_equal 1, issue.project_id
106 106 assert_equal 2, issue.tracker_id
107 107 assert_equal 3, issue.status_id
108 108 assert_equal 'API test', issue.subject
109 109
110 110 assert_response :created
111 111 assert_equal 'application/xml', @response.content_type
112 112 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
113 113 end
114 114 end
115 115
116 116 context "POST /issues.xml with failure" do
117 117 should_allow_api_authentication(:post,
118 118 '/issues.xml',
119 119 {:issue => {:project_id => 1}},
120 120 {:success_code => :unprocessable_entity})
121 121
122 122 should "have an errors tag" do
123 123 assert_no_difference('Issue.count') do
124 124 post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
125 125 end
126 126
127 127 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
128 128 end
129 129 end
130 130
131 131 context "POST /issues.json" do
132 132 should_allow_api_authentication(:post,
133 133 '/issues.json',
134 134 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
135 135 {:success_code => :created})
136 136
137 137 should "create an issue with the attributes" do
138 138 assert_difference('Issue.count') do
139 139 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
140 140 end
141 141
142 142 issue = Issue.first(:order => 'id DESC')
143 143 assert_equal 1, issue.project_id
144 144 assert_equal 2, issue.tracker_id
145 145 assert_equal 3, issue.status_id
146 146 assert_equal 'API test', issue.subject
147 147 end
148 148
149 149 end
150 150
151 151 context "POST /issues.json with failure" do
152 152 should_allow_api_authentication(:post,
153 153 '/issues.json',
154 154 {:issue => {:project_id => 1}},
155 155 {:success_code => :unprocessable_entity})
156 156
157 157 should "have an errors element" do
158 158 assert_no_difference('Issue.count') do
159 159 post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
160 160 end
161 161
162 162 json = ActiveSupport::JSON.decode(response.body)
163 assert_equal "can't be blank", json.first['subject']
163 assert json['errors'].include?(['subject', "can't be blank"])
164 164 end
165 165 end
166 166
167 167 # Issue 6 is on a private project
168 168 context "PUT /issues/6.xml" do
169 169 setup do
170 170 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
171 171 @headers = { :authorization => credentials('jsmith') }
172 172 end
173 173
174 174 should_allow_api_authentication(:put,
175 175 '/issues/6.xml',
176 176 {:issue => {:subject => 'API update', :notes => 'A new note'}},
177 177 {:success_code => :ok})
178 178
179 179 should "not create a new issue" do
180 180 assert_no_difference('Issue.count') do
181 181 put '/issues/6.xml', @parameters, @headers
182 182 end
183 183 end
184 184
185 185 should "create a new journal" do
186 186 assert_difference('Journal.count') do
187 187 put '/issues/6.xml', @parameters, @headers
188 188 end
189 189 end
190 190
191 191 should "add the note to the journal" do
192 192 put '/issues/6.xml', @parameters, @headers
193 193
194 194 journal = Journal.last
195 195 assert_equal "A new note", journal.notes
196 196 end
197 197
198 198 should "update the issue" do
199 199 put '/issues/6.xml', @parameters, @headers
200 200
201 201 issue = Issue.find(6)
202 202 assert_equal "API update", issue.subject
203 203 end
204 204
205 205 end
206 206
207 207 context "PUT /issues/6.xml with failed update" do
208 208 setup do
209 209 @parameters = {:issue => {:subject => ''}}
210 210 @headers = { :authorization => credentials('jsmith') }
211 211 end
212 212
213 213 should_allow_api_authentication(:put,
214 214 '/issues/6.xml',
215 215 {:issue => {:subject => ''}}, # Missing subject should fail
216 216 {:success_code => :unprocessable_entity})
217 217
218 218 should "not create a new issue" do
219 219 assert_no_difference('Issue.count') do
220 220 put '/issues/6.xml', @parameters, @headers
221 221 end
222 222 end
223 223
224 224 should "not create a new journal" do
225 225 assert_no_difference('Journal.count') do
226 226 put '/issues/6.xml', @parameters, @headers
227 227 end
228 228 end
229 229
230 230 should "have an errors tag" do
231 231 put '/issues/6.xml', @parameters, @headers
232 232
233 233 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
234 234 end
235 235 end
236 236
237 237 context "PUT /issues/6.json" do
238 238 setup do
239 239 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
240 240 @headers = { :authorization => credentials('jsmith') }
241 241 end
242 242
243 243 should_allow_api_authentication(:put,
244 244 '/issues/6.json',
245 245 {:issue => {:subject => 'API update', :notes => 'A new note'}},
246 246 {:success_code => :ok})
247 247
248 248 should "not create a new issue" do
249 249 assert_no_difference('Issue.count') do
250 250 put '/issues/6.json', @parameters, @headers
251 251 end
252 252 end
253 253
254 254 should "create a new journal" do
255 255 assert_difference('Journal.count') do
256 256 put '/issues/6.json', @parameters, @headers
257 257 end
258 258 end
259 259
260 260 should "add the note to the journal" do
261 261 put '/issues/6.json', @parameters, @headers
262 262
263 263 journal = Journal.last
264 264 assert_equal "A new note", journal.notes
265 265 end
266 266
267 267 should "update the issue" do
268 268 put '/issues/6.json', @parameters, @headers
269 269
270 270 issue = Issue.find(6)
271 271 assert_equal "API update", issue.subject
272 272 end
273 273
274 274 end
275 275
276 276 context "PUT /issues/6.json with failed update" do
277 277 setup do
278 278 @parameters = {:issue => {:subject => ''}}
279 279 @headers = { :authorization => credentials('jsmith') }
280 280 end
281 281
282 282 should_allow_api_authentication(:put,
283 283 '/issues/6.json',
284 284 {:issue => {:subject => ''}}, # Missing subject should fail
285 285 {:success_code => :unprocessable_entity})
286 286
287 287 should "not create a new issue" do
288 288 assert_no_difference('Issue.count') do
289 289 put '/issues/6.json', @parameters, @headers
290 290 end
291 291 end
292 292
293 293 should "not create a new journal" do
294 294 assert_no_difference('Journal.count') do
295 295 put '/issues/6.json', @parameters, @headers
296 296 end
297 297 end
298 298
299 299 should "have an errors attribute" do
300 300 put '/issues/6.json', @parameters, @headers
301 301
302 302 json = ActiveSupport::JSON.decode(response.body)
303 assert_equal "can't be blank", json.first['subject']
303 assert json['errors'].include?(['subject', "can't be blank"])
304 304 end
305 305 end
306 306
307 307 context "DELETE /issues/1.xml" do
308 308 should_allow_api_authentication(:delete,
309 309 '/issues/6.xml',
310 310 {},
311 311 {:success_code => :ok})
312 312
313 313 should "delete the issue" do
314 314 assert_difference('Issue.count',-1) do
315 315 delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
316 316 end
317 317
318 318 assert_nil Issue.find_by_id(6)
319 319 end
320 320 end
321 321
322 322 context "DELETE /issues/1.json" do
323 323 should_allow_api_authentication(:delete,
324 324 '/issues/6.json',
325 325 {},
326 326 {:success_code => :ok})
327 327
328 328 should "delete the issue" do
329 329 assert_difference('Issue.count',-1) do
330 330 delete '/issues/6.json', {}, :authorization => credentials('jsmith')
331 331 end
332 332
333 333 assert_nil Issue.find_by_id(6)
334 334 end
335 335 end
336 336
337 337 def credentials(user, password=nil)
338 338 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
339 339 end
340 340 end
@@ -1,54 +1,63
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../../../../../test_helper'
19 19
20 20 class Redmine::Views::Builders::JsonTest < HelperTestCase
21 21
22 22 def test_hash
23 23 assert_json_output({'person' => {'name' => 'Ryan', 'age' => 32}}) do |b|
24 24 b.person do
25 25 b.name 'Ryan'
26 26 b.age 32
27 27 end
28 28 end
29 29 end
30 30
31 def test_hash_hash
32 assert_json_output({'person' => {'name' => 'Ryan', 'birth' => {'city' => 'London', 'country' => 'UK'}}}) do |b|
33 b.person do
34 b.name 'Ryan'
35 b.birth :city => 'London', :country => 'UK'
36 end
37 end
38 end
39
31 40 def test_array
32 41 assert_json_output({'books' => [{'title' => 'Book 1', 'author' => 'B. Smith'}, {'title' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b|
33 42 b.array :books do |b|
34 43 b.book :title => 'Book 1', :author => 'B. Smith'
35 44 b.book :title => 'Book 2', :author => 'G. Cooper'
36 45 end
37 46 end
38 47 end
39 48
40 49 def test_array_with_content_tags
41 50 assert_json_output({'books' => [{'value' => 'Book 1', 'author' => 'B. Smith'}, {'value' => 'Book 2', 'author' => 'G. Cooper'}]}) do |b|
42 51 b.array :books do |b|
43 52 b.book 'Book 1', :author => 'B. Smith'
44 53 b.book 'Book 2', :author => 'G. Cooper'
45 54 end
46 55 end
47 56 end
48 57
49 58 def assert_json_output(expected, &block)
50 59 builder = Redmine::Views::Builders::Json.new
51 60 block.call(builder)
52 61 assert_equal(expected, ActiveSupport::JSON.decode(builder.output))
53 62 end
54 63 end
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now