##// END OF EJS Templates
Makes projects API return XML description when creating a project (#6874)....
Jean-Philippe Lang -
r4283:eaab2ede76c9
parent child
Show More
@@ -1,271 +1,271
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 menu_item :overview
19 menu_item :overview
20 menu_item :roadmap, :only => :roadmap
20 menu_item :roadmap, :only => :roadmap
21 menu_item :settings, :only => :settings
21 menu_item :settings, :only => :settings
22
22
23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
25 before_filter :authorize_global, :only => [:new, :create]
25 before_filter :authorize_global, :only => [:new, :create]
26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
27 accept_key_auth :index
27 accept_key_auth :index
28
28
29 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
29 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
30 if controller.request.post?
30 if controller.request.post?
31 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
31 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
32 end
32 end
33 end
33 end
34
34
35 # TODO: convert to PUT only
35 # TODO: convert to PUT only
36 verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
36 verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
37
37
38 helper :sort
38 helper :sort
39 include SortHelper
39 include SortHelper
40 helper :custom_fields
40 helper :custom_fields
41 include CustomFieldsHelper
41 include CustomFieldsHelper
42 helper :issues
42 helper :issues
43 helper :queries
43 helper :queries
44 include QueriesHelper
44 include QueriesHelper
45 helper :repositories
45 helper :repositories
46 include RepositoriesHelper
46 include RepositoriesHelper
47 include ProjectsHelper
47 include ProjectsHelper
48
48
49 # Lists visible projects
49 # Lists visible projects
50 def index
50 def index
51 respond_to do |format|
51 respond_to do |format|
52 format.html {
52 format.html {
53 @projects = Project.visible.find(:all, :order => 'lft')
53 @projects = Project.visible.find(:all, :order => 'lft')
54 }
54 }
55 format.xml {
55 format.xml {
56 @projects = Project.visible.find(:all, :order => 'lft')
56 @projects = Project.visible.find(:all, :order => 'lft')
57 }
57 }
58 format.atom {
58 format.atom {
59 projects = Project.visible.find(:all, :order => 'created_on DESC',
59 projects = Project.visible.find(:all, :order => 'created_on DESC',
60 :limit => Setting.feeds_limit.to_i)
60 :limit => Setting.feeds_limit.to_i)
61 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
61 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
62 }
62 }
63 end
63 end
64 end
64 end
65
65
66 def new
66 def new
67 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
68 @trackers = Tracker.all
68 @trackers = Tracker.all
69 @project = Project.new(params[:project])
69 @project = Project.new(params[:project])
70
70
71 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
71 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
72 @project.trackers = Tracker.all
72 @project.trackers = Tracker.all
73 @project.is_public = Setting.default_projects_public?
73 @project.is_public = Setting.default_projects_public?
74 @project.enabled_module_names = Setting.default_projects_modules
74 @project.enabled_module_names = Setting.default_projects_modules
75 end
75 end
76
76
77 def create
77 def create
78 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
78 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
79 @trackers = Tracker.all
79 @trackers = Tracker.all
80 @project = Project.new(params[:project])
80 @project = Project.new(params[:project])
81
81
82 @project.enabled_module_names = params[:enabled_modules]
82 @project.enabled_module_names = params[:enabled_modules]
83 if validate_parent_id && @project.save
83 if validate_parent_id && @project.save
84 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
84 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
85 # Add current user as a project member if he is not admin
85 # Add current user as a project member if he is not admin
86 unless User.current.admin?
86 unless User.current.admin?
87 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
87 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
88 m = Member.new(:user => User.current, :roles => [r])
88 m = Member.new(:user => User.current, :roles => [r])
89 @project.members << m
89 @project.members << m
90 end
90 end
91 respond_to do |format|
91 respond_to do |format|
92 format.html {
92 format.html {
93 flash[:notice] = l(:notice_successful_create)
93 flash[:notice] = l(:notice_successful_create)
94 redirect_to :controller => 'projects', :action => 'settings', :id => @project
94 redirect_to :controller => 'projects', :action => 'settings', :id => @project
95 }
95 }
96 format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
96 format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
97 end
97 end
98 else
98 else
99 respond_to do |format|
99 respond_to do |format|
100 format.html { render :action => 'new' }
100 format.html { render :action => 'new' }
101 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
101 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
102 end
102 end
103 end
103 end
104
104
105 end
105 end
106
106
107 def copy
107 def copy
108 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
108 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
109 @trackers = Tracker.all
109 @trackers = Tracker.all
110 @root_projects = Project.find(:all,
110 @root_projects = Project.find(:all,
111 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
111 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
112 :order => 'name')
112 :order => 'name')
113 @source_project = Project.find(params[:id])
113 @source_project = Project.find(params[:id])
114 if request.get?
114 if request.get?
115 @project = Project.copy_from(@source_project)
115 @project = Project.copy_from(@source_project)
116 if @project
116 if @project
117 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
117 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
118 else
118 else
119 redirect_to :controller => 'admin', :action => 'projects'
119 redirect_to :controller => 'admin', :action => 'projects'
120 end
120 end
121 else
121 else
122 Mailer.with_deliveries(params[:notifications] == '1') do
122 Mailer.with_deliveries(params[:notifications] == '1') do
123 @project = Project.new(params[:project])
123 @project = Project.new(params[:project])
124 @project.enabled_module_names = params[:enabled_modules]
124 @project.enabled_module_names = params[:enabled_modules]
125 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
125 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
126 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
126 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
127 flash[:notice] = l(:notice_successful_create)
127 flash[:notice] = l(:notice_successful_create)
128 redirect_to :controller => 'projects', :action => 'settings'
128 redirect_to :controller => 'projects', :action => 'settings'
129 elsif !@project.new_record?
129 elsif !@project.new_record?
130 # Project was created
130 # Project was created
131 # But some objects were not copied due to validation failures
131 # But some objects were not copied due to validation failures
132 # (eg. issues from disabled trackers)
132 # (eg. issues from disabled trackers)
133 # TODO: inform about that
133 # TODO: inform about that
134 redirect_to :controller => 'projects', :action => 'settings'
134 redirect_to :controller => 'projects', :action => 'settings'
135 end
135 end
136 end
136 end
137 end
137 end
138 rescue ActiveRecord::RecordNotFound
138 rescue ActiveRecord::RecordNotFound
139 redirect_to :controller => 'admin', :action => 'projects'
139 redirect_to :controller => 'admin', :action => 'projects'
140 end
140 end
141
141
142 # Show @project
142 # Show @project
143 def show
143 def show
144 if params[:jump]
144 if params[:jump]
145 # try to redirect to the requested menu item
145 # try to redirect to the requested menu item
146 redirect_to_project_menu_item(@project, params[:jump]) && return
146 redirect_to_project_menu_item(@project, params[:jump]) && return
147 end
147 end
148
148
149 @users_by_role = @project.users_by_role
149 @users_by_role = @project.users_by_role
150 @subprojects = @project.children.visible
150 @subprojects = @project.children.visible
151 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
151 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
152 @trackers = @project.rolled_up_trackers
152 @trackers = @project.rolled_up_trackers
153
153
154 cond = @project.project_condition(Setting.display_subprojects_issues?)
154 cond = @project.project_condition(Setting.display_subprojects_issues?)
155
155
156 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
156 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
157 :include => [:project, :status, :tracker],
157 :include => [:project, :status, :tracker],
158 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
158 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
159 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
159 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
160 :include => [:project, :status, :tracker],
160 :include => [:project, :status, :tracker],
161 :conditions => cond)
161 :conditions => cond)
162
162
163 TimeEntry.visible_by(User.current) do
163 TimeEntry.visible_by(User.current) do
164 @total_hours = TimeEntry.sum(:hours,
164 @total_hours = TimeEntry.sum(:hours,
165 :include => :project,
165 :include => :project,
166 :conditions => cond).to_f
166 :conditions => cond).to_f
167 end
167 end
168 @key = User.current.rss_key
168 @key = User.current.rss_key
169
169
170 respond_to do |format|
170 respond_to do |format|
171 format.html
171 format.html
172 format.xml
172 format.xml
173 end
173 end
174 end
174 end
175
175
176 def settings
176 def settings
177 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
177 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
178 @issue_category ||= IssueCategory.new
178 @issue_category ||= IssueCategory.new
179 @member ||= @project.members.new
179 @member ||= @project.members.new
180 @trackers = Tracker.all
180 @trackers = Tracker.all
181 @repository ||= @project.repository
181 @repository ||= @project.repository
182 @wiki ||= @project.wiki
182 @wiki ||= @project.wiki
183 end
183 end
184
184
185 def edit
185 def edit
186 end
186 end
187
187
188 def update
188 def update
189 @project.attributes = params[:project]
189 @project.attributes = params[:project]
190 if validate_parent_id && @project.save
190 if validate_parent_id && @project.save
191 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
191 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
192 respond_to do |format|
192 respond_to do |format|
193 format.html {
193 format.html {
194 flash[:notice] = l(:notice_successful_update)
194 flash[:notice] = l(:notice_successful_update)
195 redirect_to :action => 'settings', :id => @project
195 redirect_to :action => 'settings', :id => @project
196 }
196 }
197 format.xml { head :ok }
197 format.xml { head :ok }
198 end
198 end
199 else
199 else
200 respond_to do |format|
200 respond_to do |format|
201 format.html {
201 format.html {
202 settings
202 settings
203 render :action => 'settings'
203 render :action => 'settings'
204 }
204 }
205 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
205 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
206 end
206 end
207 end
207 end
208 end
208 end
209
209
210 def modules
210 def modules
211 @project.enabled_module_names = params[:enabled_modules]
211 @project.enabled_module_names = params[:enabled_modules]
212 flash[:notice] = l(:notice_successful_update)
212 flash[:notice] = l(:notice_successful_update)
213 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
213 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
214 end
214 end
215
215
216 def archive
216 def archive
217 if request.post?
217 if request.post?
218 unless @project.archive
218 unless @project.archive
219 flash[:error] = l(:error_can_not_archive_project)
219 flash[:error] = l(:error_can_not_archive_project)
220 end
220 end
221 end
221 end
222 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
222 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
223 end
223 end
224
224
225 def unarchive
225 def unarchive
226 @project.unarchive if request.post? && !@project.active?
226 @project.unarchive if request.post? && !@project.active?
227 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
227 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
228 end
228 end
229
229
230 # Delete @project
230 # Delete @project
231 def destroy
231 def destroy
232 @project_to_destroy = @project
232 @project_to_destroy = @project
233 if request.get?
233 if request.get?
234 # display confirmation view
234 # display confirmation view
235 else
235 else
236 if params[:format] == 'xml' || params[:confirm]
236 if params[:format] == 'xml' || params[:confirm]
237 @project_to_destroy.destroy
237 @project_to_destroy.destroy
238 respond_to do |format|
238 respond_to do |format|
239 format.html { redirect_to :controller => 'admin', :action => 'projects' }
239 format.html { redirect_to :controller => 'admin', :action => 'projects' }
240 format.xml { head :ok }
240 format.xml { head :ok }
241 end
241 end
242 end
242 end
243 end
243 end
244 # hide project in layout
244 # hide project in layout
245 @project = nil
245 @project = nil
246 end
246 end
247
247
248 private
248 private
249 def find_optional_project
249 def find_optional_project
250 return true unless params[:id]
250 return true unless params[:id]
251 @project = Project.find(params[:id])
251 @project = Project.find(params[:id])
252 authorize
252 authorize
253 rescue ActiveRecord::RecordNotFound
253 rescue ActiveRecord::RecordNotFound
254 render_404
254 render_404
255 end
255 end
256
256
257 # Validates parent_id param according to user's permissions
257 # Validates parent_id param according to user's permissions
258 # TODO: move it to Project model in a validation that depends on User.current
258 # TODO: move it to Project model in a validation that depends on User.current
259 def validate_parent_id
259 def validate_parent_id
260 return true if User.current.admin?
260 return true if User.current.admin?
261 parent_id = params[:project] && params[:project][:parent_id]
261 parent_id = params[:project] && params[:project][:parent_id]
262 if parent_id || @project.new_record?
262 if parent_id || @project.new_record?
263 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
263 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
264 unless @project.allowed_parents.include?(parent)
264 unless @project.allowed_parents.include?(parent)
265 @project.errors.add :parent_id, :invalid
265 @project.errors.add :parent_id, :invalid
266 return false
266 return false
267 end
267 end
268 end
268 end
269 true
269 true
270 end
270 end
271 end
271 end
@@ -1,336 +1,340
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 should_allow_api_authentication(:post,
94 should_allow_api_authentication(:post,
95 '/issues.xml',
95 '/issues.xml',
96 {:issue => {: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 {:success_code => :created})
97 {:success_code => :created})
98
98
99 should "create an issue with the attributes" do
99 should "create an issue with the attributes" do
100 assert_difference('Issue.count') do
100 assert_difference('Issue.count') do
101 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
101 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
102 end
102 end
103
103
104 issue = Issue.first(:order => 'id DESC')
104 issue = Issue.first(:order => 'id DESC')
105 assert_equal 1, issue.project_id
105 assert_equal 1, issue.project_id
106 assert_equal 2, issue.tracker_id
106 assert_equal 2, issue.tracker_id
107 assert_equal 3, issue.status_id
107 assert_equal 3, issue.status_id
108 assert_equal 'API test', issue.subject
108 assert_equal 'API test', issue.subject
109
110 assert_response :created
111 assert_equal 'application/xml', @response.content_type
112 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
109 end
113 end
110 end
114 end
111
115
112 context "POST /issues.xml with failure" do
116 context "POST /issues.xml with failure" do
113 should_allow_api_authentication(:post,
117 should_allow_api_authentication(:post,
114 '/issues.xml',
118 '/issues.xml',
115 {:issue => {:project_id => 1}},
119 {:issue => {:project_id => 1}},
116 {:success_code => :unprocessable_entity})
120 {:success_code => :unprocessable_entity})
117
121
118 should "have an errors tag" do
122 should "have an errors tag" do
119 assert_no_difference('Issue.count') do
123 assert_no_difference('Issue.count') do
120 post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
124 post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
121 end
125 end
122
126
123 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
127 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
124 end
128 end
125 end
129 end
126
130
127 context "POST /issues.json" do
131 context "POST /issues.json" do
128 should_allow_api_authentication(:post,
132 should_allow_api_authentication(:post,
129 '/issues.json',
133 '/issues.json',
130 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
134 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
131 {:success_code => :created})
135 {:success_code => :created})
132
136
133 should "create an issue with the attributes" do
137 should "create an issue with the attributes" do
134 assert_difference('Issue.count') do
138 assert_difference('Issue.count') do
135 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
139 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
136 end
140 end
137
141
138 issue = Issue.first(:order => 'id DESC')
142 issue = Issue.first(:order => 'id DESC')
139 assert_equal 1, issue.project_id
143 assert_equal 1, issue.project_id
140 assert_equal 2, issue.tracker_id
144 assert_equal 2, issue.tracker_id
141 assert_equal 3, issue.status_id
145 assert_equal 3, issue.status_id
142 assert_equal 'API test', issue.subject
146 assert_equal 'API test', issue.subject
143 end
147 end
144
148
145 end
149 end
146
150
147 context "POST /issues.json with failure" do
151 context "POST /issues.json with failure" do
148 should_allow_api_authentication(:post,
152 should_allow_api_authentication(:post,
149 '/issues.json',
153 '/issues.json',
150 {:issue => {:project_id => 1}},
154 {:issue => {:project_id => 1}},
151 {:success_code => :unprocessable_entity})
155 {:success_code => :unprocessable_entity})
152
156
153 should "have an errors element" do
157 should "have an errors element" do
154 assert_no_difference('Issue.count') do
158 assert_no_difference('Issue.count') do
155 post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
159 post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
156 end
160 end
157
161
158 json = ActiveSupport::JSON.decode(response.body)
162 json = ActiveSupport::JSON.decode(response.body)
159 assert_equal "can't be blank", json.first['subject']
163 assert_equal "can't be blank", json.first['subject']
160 end
164 end
161 end
165 end
162
166
163 # Issue 6 is on a private project
167 # Issue 6 is on a private project
164 context "PUT /issues/6.xml" do
168 context "PUT /issues/6.xml" do
165 setup do
169 setup do
166 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
170 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
167 @headers = { :authorization => credentials('jsmith') }
171 @headers = { :authorization => credentials('jsmith') }
168 end
172 end
169
173
170 should_allow_api_authentication(:put,
174 should_allow_api_authentication(:put,
171 '/issues/6.xml',
175 '/issues/6.xml',
172 {:issue => {:subject => 'API update', :notes => 'A new note'}},
176 {:issue => {:subject => 'API update', :notes => 'A new note'}},
173 {:success_code => :ok})
177 {:success_code => :ok})
174
178
175 should "not create a new issue" do
179 should "not create a new issue" do
176 assert_no_difference('Issue.count') do
180 assert_no_difference('Issue.count') do
177 put '/issues/6.xml', @parameters, @headers
181 put '/issues/6.xml', @parameters, @headers
178 end
182 end
179 end
183 end
180
184
181 should "create a new journal" do
185 should "create a new journal" do
182 assert_difference('Journal.count') do
186 assert_difference('Journal.count') do
183 put '/issues/6.xml', @parameters, @headers
187 put '/issues/6.xml', @parameters, @headers
184 end
188 end
185 end
189 end
186
190
187 should "add the note to the journal" do
191 should "add the note to the journal" do
188 put '/issues/6.xml', @parameters, @headers
192 put '/issues/6.xml', @parameters, @headers
189
193
190 journal = Journal.last
194 journal = Journal.last
191 assert_equal "A new note", journal.notes
195 assert_equal "A new note", journal.notes
192 end
196 end
193
197
194 should "update the issue" do
198 should "update the issue" do
195 put '/issues/6.xml', @parameters, @headers
199 put '/issues/6.xml', @parameters, @headers
196
200
197 issue = Issue.find(6)
201 issue = Issue.find(6)
198 assert_equal "API update", issue.subject
202 assert_equal "API update", issue.subject
199 end
203 end
200
204
201 end
205 end
202
206
203 context "PUT /issues/6.xml with failed update" do
207 context "PUT /issues/6.xml with failed update" do
204 setup do
208 setup do
205 @parameters = {:issue => {:subject => ''}}
209 @parameters = {:issue => {:subject => ''}}
206 @headers = { :authorization => credentials('jsmith') }
210 @headers = { :authorization => credentials('jsmith') }
207 end
211 end
208
212
209 should_allow_api_authentication(:put,
213 should_allow_api_authentication(:put,
210 '/issues/6.xml',
214 '/issues/6.xml',
211 {:issue => {:subject => ''}}, # Missing subject should fail
215 {:issue => {:subject => ''}}, # Missing subject should fail
212 {:success_code => :unprocessable_entity})
216 {:success_code => :unprocessable_entity})
213
217
214 should "not create a new issue" do
218 should "not create a new issue" do
215 assert_no_difference('Issue.count') do
219 assert_no_difference('Issue.count') do
216 put '/issues/6.xml', @parameters, @headers
220 put '/issues/6.xml', @parameters, @headers
217 end
221 end
218 end
222 end
219
223
220 should "not create a new journal" do
224 should "not create a new journal" do
221 assert_no_difference('Journal.count') do
225 assert_no_difference('Journal.count') do
222 put '/issues/6.xml', @parameters, @headers
226 put '/issues/6.xml', @parameters, @headers
223 end
227 end
224 end
228 end
225
229
226 should "have an errors tag" do
230 should "have an errors tag" do
227 put '/issues/6.xml', @parameters, @headers
231 put '/issues/6.xml', @parameters, @headers
228
232
229 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
233 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
230 end
234 end
231 end
235 end
232
236
233 context "PUT /issues/6.json" do
237 context "PUT /issues/6.json" do
234 setup do
238 setup do
235 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
239 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
236 @headers = { :authorization => credentials('jsmith') }
240 @headers = { :authorization => credentials('jsmith') }
237 end
241 end
238
242
239 should_allow_api_authentication(:put,
243 should_allow_api_authentication(:put,
240 '/issues/6.json',
244 '/issues/6.json',
241 {:issue => {:subject => 'API update', :notes => 'A new note'}},
245 {:issue => {:subject => 'API update', :notes => 'A new note'}},
242 {:success_code => :ok})
246 {:success_code => :ok})
243
247
244 should "not create a new issue" do
248 should "not create a new issue" do
245 assert_no_difference('Issue.count') do
249 assert_no_difference('Issue.count') do
246 put '/issues/6.json', @parameters, @headers
250 put '/issues/6.json', @parameters, @headers
247 end
251 end
248 end
252 end
249
253
250 should "create a new journal" do
254 should "create a new journal" do
251 assert_difference('Journal.count') do
255 assert_difference('Journal.count') do
252 put '/issues/6.json', @parameters, @headers
256 put '/issues/6.json', @parameters, @headers
253 end
257 end
254 end
258 end
255
259
256 should "add the note to the journal" do
260 should "add the note to the journal" do
257 put '/issues/6.json', @parameters, @headers
261 put '/issues/6.json', @parameters, @headers
258
262
259 journal = Journal.last
263 journal = Journal.last
260 assert_equal "A new note", journal.notes
264 assert_equal "A new note", journal.notes
261 end
265 end
262
266
263 should "update the issue" do
267 should "update the issue" do
264 put '/issues/6.json', @parameters, @headers
268 put '/issues/6.json', @parameters, @headers
265
269
266 issue = Issue.find(6)
270 issue = Issue.find(6)
267 assert_equal "API update", issue.subject
271 assert_equal "API update", issue.subject
268 end
272 end
269
273
270 end
274 end
271
275
272 context "PUT /issues/6.json with failed update" do
276 context "PUT /issues/6.json with failed update" do
273 setup do
277 setup do
274 @parameters = {:issue => {:subject => ''}}
278 @parameters = {:issue => {:subject => ''}}
275 @headers = { :authorization => credentials('jsmith') }
279 @headers = { :authorization => credentials('jsmith') }
276 end
280 end
277
281
278 should_allow_api_authentication(:put,
282 should_allow_api_authentication(:put,
279 '/issues/6.json',
283 '/issues/6.json',
280 {:issue => {:subject => ''}}, # Missing subject should fail
284 {:issue => {:subject => ''}}, # Missing subject should fail
281 {:success_code => :unprocessable_entity})
285 {:success_code => :unprocessable_entity})
282
286
283 should "not create a new issue" do
287 should "not create a new issue" do
284 assert_no_difference('Issue.count') do
288 assert_no_difference('Issue.count') do
285 put '/issues/6.json', @parameters, @headers
289 put '/issues/6.json', @parameters, @headers
286 end
290 end
287 end
291 end
288
292
289 should "not create a new journal" do
293 should "not create a new journal" do
290 assert_no_difference('Journal.count') do
294 assert_no_difference('Journal.count') do
291 put '/issues/6.json', @parameters, @headers
295 put '/issues/6.json', @parameters, @headers
292 end
296 end
293 end
297 end
294
298
295 should "have an errors attribute" do
299 should "have an errors attribute" do
296 put '/issues/6.json', @parameters, @headers
300 put '/issues/6.json', @parameters, @headers
297
301
298 json = ActiveSupport::JSON.decode(response.body)
302 json = ActiveSupport::JSON.decode(response.body)
299 assert_equal "can't be blank", json.first['subject']
303 assert_equal "can't be blank", json.first['subject']
300 end
304 end
301 end
305 end
302
306
303 context "DELETE /issues/1.xml" do
307 context "DELETE /issues/1.xml" do
304 should_allow_api_authentication(:delete,
308 should_allow_api_authentication(:delete,
305 '/issues/6.xml',
309 '/issues/6.xml',
306 {},
310 {},
307 {:success_code => :ok})
311 {:success_code => :ok})
308
312
309 should "delete the issue" do
313 should "delete the issue" do
310 assert_difference('Issue.count',-1) do
314 assert_difference('Issue.count',-1) do
311 delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
315 delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
312 end
316 end
313
317
314 assert_nil Issue.find_by_id(6)
318 assert_nil Issue.find_by_id(6)
315 end
319 end
316 end
320 end
317
321
318 context "DELETE /issues/1.json" do
322 context "DELETE /issues/1.json" do
319 should_allow_api_authentication(:delete,
323 should_allow_api_authentication(:delete,
320 '/issues/6.json',
324 '/issues/6.json',
321 {},
325 {},
322 {:success_code => :ok})
326 {:success_code => :ok})
323
327
324 should "delete the issue" do
328 should "delete the issue" do
325 assert_difference('Issue.count',-1) do
329 assert_difference('Issue.count',-1) do
326 delete '/issues/6.json', {}, :authorization => credentials('jsmith')
330 delete '/issues/6.json', {}, :authorization => credentials('jsmith')
327 end
331 end
328
332
329 assert_nil Issue.find_by_id(6)
333 assert_nil Issue.find_by_id(6)
330 end
334 end
331 end
335 end
332
336
333 def credentials(user, password=nil)
337 def credentials(user, password=nil)
334 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
338 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
335 end
339 end
336 end
340 end
@@ -1,108 +1,111
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::ProjectsTest < ActionController::IntegrationTest
20 class ApiTest::ProjectsTest < ActionController::IntegrationTest
21 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
21 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
23 :attachments, :custom_fields, :custom_values, :time_entries
23 :attachments, :custom_fields, :custom_values, :time_entries
24
24
25 def setup
25 def setup
26 Setting.rest_api_enabled = '1'
26 Setting.rest_api_enabled = '1'
27 end
27 end
28
28
29 def test_index
29 def test_index
30 get '/projects.xml'
30 get '/projects.xml'
31 assert_response :success
31 assert_response :success
32 assert_equal 'application/xml', @response.content_type
32 assert_equal 'application/xml', @response.content_type
33 end
33 end
34
34
35 def test_show
35 def test_show
36 get '/projects/1.xml'
36 get '/projects/1.xml'
37 assert_response :success
37 assert_response :success
38 assert_equal 'application/xml', @response.content_type
38 assert_equal 'application/xml', @response.content_type
39 assert_tag 'custom_field', :attributes => {:name => 'Development status'}, :content => 'Stable'
39 assert_tag 'custom_field', :attributes => {:name => 'Development status'}, :content => 'Stable'
40 end
40 end
41
41
42 def test_show_should_not_display_hidden_custom_fields
42 def test_show_should_not_display_hidden_custom_fields
43 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
43 ProjectCustomField.find_by_name('Development status').update_attribute :visible, false
44 get '/projects/1.xml'
44 get '/projects/1.xml'
45 assert_response :success
45 assert_response :success
46 assert_equal 'application/xml', @response.content_type
46 assert_equal 'application/xml', @response.content_type
47 assert_no_tag 'custom_field', :attributes => {:name => 'Development status'}
47 assert_no_tag 'custom_field', :attributes => {:name => 'Development status'}
48 end
48 end
49
49
50 def test_create
50 def test_create
51 attributes = {:name => 'API test', :identifier => 'api-test'}
51 attributes = {:name => 'API test', :identifier => 'api-test'}
52 assert_difference 'Project.count' do
52 assert_difference 'Project.count' do
53 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
53 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
54 end
54 end
55 assert_response :created
55
56 assert_equal 'application/xml', @response.content_type
57 project = Project.first(:order => 'id DESC')
56 project = Project.first(:order => 'id DESC')
58 attributes.each do |attribute, value|
57 attributes.each do |attribute, value|
59 assert_equal value, project.send(attribute)
58 assert_equal value, project.send(attribute)
60 end
59 end
60
61 assert_response :created
62 assert_equal 'application/xml', @response.content_type
63 assert_tag 'project', :child => {:tag => 'id', :content => project.id.to_s}
61 end
64 end
62
65
63 def test_create_failure
66 def test_create_failure
64 attributes = {:name => 'API test'}
67 attributes = {:name => 'API test'}
65 assert_no_difference 'Project.count' do
68 assert_no_difference 'Project.count' do
66 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
69 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
67 end
70 end
68 assert_response :unprocessable_entity
71 assert_response :unprocessable_entity
69 assert_equal 'application/xml', @response.content_type
72 assert_equal 'application/xml', @response.content_type
70 assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
73 assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
71 end
74 end
72
75
73 def test_update
76 def test_update
74 attributes = {:name => 'API update'}
77 attributes = {:name => 'API update'}
75 assert_no_difference 'Project.count' do
78 assert_no_difference 'Project.count' do
76 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
79 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
77 end
80 end
78 assert_response :ok
81 assert_response :ok
79 assert_equal 'application/xml', @response.content_type
82 assert_equal 'application/xml', @response.content_type
80 project = Project.find(1)
83 project = Project.find(1)
81 attributes.each do |attribute, value|
84 attributes.each do |attribute, value|
82 assert_equal value, project.send(attribute)
85 assert_equal value, project.send(attribute)
83 end
86 end
84 end
87 end
85
88
86 def test_update_failure
89 def test_update_failure
87 attributes = {:name => ''}
90 attributes = {:name => ''}
88 assert_no_difference 'Project.count' do
91 assert_no_difference 'Project.count' do
89 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
92 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
90 end
93 end
91 assert_response :unprocessable_entity
94 assert_response :unprocessable_entity
92 assert_equal 'application/xml', @response.content_type
95 assert_equal 'application/xml', @response.content_type
93 assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
96 assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
94 end
97 end
95
98
96 def test_destroy
99 def test_destroy
97 assert_difference 'Project.count', -1 do
100 assert_difference 'Project.count', -1 do
98 delete '/projects/2.xml', {}, :authorization => credentials('admin')
101 delete '/projects/2.xml', {}, :authorization => credentials('admin')
99 end
102 end
100 assert_response :ok
103 assert_response :ok
101 assert_equal 'application/xml', @response.content_type
104 assert_equal 'application/xml', @response.content_type
102 assert_nil Project.find_by_id(2)
105 assert_nil Project.find_by_id(2)
103 end
106 end
104
107
105 def credentials(user, password=nil)
108 def credentials(user, password=nil)
106 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
109 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
107 end
110 end
108 end
111 end
General Comments 0
You need to be logged in to leave comments. Login now