##// END OF EJS Templates
Add/remove issue watchers via the REST API (#6727)....
Jean-Philippe Lang -
r11060:a0158eff9643
parent child
Show More
@@ -1,95 +1,101
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 WatchersController < ApplicationController
19 19 before_filter :find_project
20 20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
21 21 before_filter :authorize, :only => [:new, :destroy]
22 accept_api_auth :create, :destroy
22 23
23 24 def watch
24 25 if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
25 26 render_403
26 27 else
27 28 set_watcher(User.current, true)
28 29 end
29 30 end
30 31
31 32 def unwatch
32 33 set_watcher(User.current, false)
33 34 end
34 35
35 36 def new
36 37 end
37 38
38 39 def create
39 if params[:watcher].is_a?(Hash) && request.post?
40 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
41 user_ids.each do |user_id|
42 Watcher.create(:watchable => @watched, :user_id => user_id)
43 end
40 user_ids = []
41 if params[:watcher].is_a?(Hash)
42 user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id])
43 else
44 user_ids << params[:user_id]
45 end
46 user_ids.flatten.compact.uniq.each do |user_id|
47 Watcher.create(:watchable => @watched, :user_id => user_id)
44 48 end
45 49 respond_to do |format|
46 50 format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
47 51 format.js
52 format.api { render_api_ok }
48 53 end
49 54 end
50 55
51 56 def append
52 57 if params[:watcher].is_a?(Hash)
53 58 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
54 59 @users = User.active.find_all_by_id(user_ids)
55 60 end
56 61 end
57 62
58 63 def destroy
59 @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
64 @watched.set_watcher(User.find(params[:user_id]), false)
60 65 respond_to do |format|
61 66 format.html { redirect_to :back }
62 67 format.js
68 format.api { render_api_ok }
63 69 end
64 70 end
65 71
66 72 def autocomplete_for_user
67 73 @users = User.active.sorted.like(params[:q]).limit(100).all
68 74 if @watched
69 75 @users -= @watched.watcher_users
70 76 end
71 77 render :layout => false
72 78 end
73 79
74 80 private
75 81 def find_project
76 82 if params[:object_type] && params[:object_id]
77 83 klass = Object.const_get(params[:object_type].camelcase)
78 84 return false unless klass.respond_to?('watched_by')
79 85 @watched = klass.find(params[:object_id])
80 86 @project = @watched.project
81 87 elsif params[:project_id]
82 88 @project = Project.visible.find_by_param(params[:project_id])
83 89 end
84 90 rescue
85 91 render_404
86 92 end
87 93
88 94 def set_watcher(user, watching)
89 95 @watched.set_watcher(user, watching)
90 96 respond_to do |format|
91 97 format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
92 98 format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} }
93 99 end
94 100 end
95 101 end
@@ -1,67 +1,73
1 1 api.issue do
2 2 api.id @issue.id
3 3 api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil?
4 4 api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil?
5 5 api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil?
6 6 api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil?
7 7 api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil?
8 8 api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil?
9 9 api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil?
10 10 api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil?
11 11 api.parent(:id => @issue.parent_id) unless @issue.parent.nil?
12 12
13 13 api.subject @issue.subject
14 14 api.description @issue.description
15 15 api.start_date @issue.start_date
16 16 api.due_date @issue.due_date
17 17 api.done_ratio @issue.done_ratio
18 18 api.estimated_hours @issue.estimated_hours
19 19 api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project)
20 20
21 21 render_api_custom_values @issue.custom_field_values, api
22 22
23 23 api.created_on @issue.created_on
24 24 api.updated_on @issue.updated_on
25 25
26 26 render_api_issue_children(@issue, api) if include_in_api_response?('children')
27 27
28 28 api.array :attachments do
29 29 @issue.attachments.each do |attachment|
30 30 render_api_attachment(attachment, api)
31 31 end
32 32 end if include_in_api_response?('attachments')
33 33
34 34 api.array :relations do
35 35 @relations.each do |relation|
36 36 api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay)
37 37 end
38 38 end if include_in_api_response?('relations') && @relations.present?
39 39
40 40 api.array :changesets do
41 41 @issue.changesets.each do |changeset|
42 42 api.changeset :revision => changeset.revision do
43 43 api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil?
44 44 api.comments changeset.comments
45 45 api.committed_on changeset.committed_on
46 46 end
47 47 end
48 48 end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project)
49 49
50 50 api.array :journals do
51 51 @journals.each do |journal|
52 52 api.journal :id => journal.id do
53 53 api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
54 54 api.notes journal.notes
55 55 api.created_on journal.created_on
56 56 api.array :details do
57 57 journal.details.each do |detail|
58 58 api.detail :property => detail.property, :name => detail.prop_key do
59 59 api.old_value detail.old_value
60 60 api.new_value detail.value
61 61 end
62 62 end
63 63 end
64 64 end
65 65 end
66 66 end if include_in_api_response?('journals')
67
68 api.array :watchers do
69 @issue.watcher_users.each do |user|
70 api.user :id => user.id, :name => user.name
71 end
72 end if include_in_api_response?('watchers') && User.current.allowed_to?(:view_issue_watchers, @issue.project)
67 73 end
@@ -1,344 +1,347
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 RedmineApp::Application.routes.draw do
19 19 root :to => 'welcome#index', :as => 'home'
20 20
21 21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
22 22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
23 23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 25 match 'account/activate', :to => 'account#activate', :via => :get
26 26
27 27 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put]
28 28 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put]
29 29 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put]
30 30 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put]
31 31
32 32 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
33 33 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
34 34
35 35 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
36 36 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
37 37 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
38 38 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
39 39
40 40 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
41 41 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
42 42 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
43 43 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
44 44
45 45 # Misc issue routes. TODO: move into resources
46 46 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
47 47 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
48 48 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
49 49 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
50 50
51 51 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
52 52 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
53 53
54 54 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
55 55 get '/issues/gantt', :to => 'gantts#show'
56 56
57 57 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
58 58 get '/issues/calendar', :to => 'calendars#show'
59 59
60 60 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
61 61 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
62 62
63 63 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
64 64 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
65 65 match 'my/page', :controller => 'my', :action => 'page', :via => :get
66 66 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
67 67 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
68 68 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
69 69 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
70 70 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
71 71 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
72 72 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
73 73 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
74 74
75 75 resources :users
76 76 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
77 77 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
78 78 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
79 79
80 80 match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get
81 81 match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post
82 82 match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post
83 83 match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post
84 84 match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post
85 85 match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post
86 86 match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get
87 # Specific routes for issue watchers API
88 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
89 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
87 90
88 91 resources :projects do
89 92 member do
90 93 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
91 94 post 'modules'
92 95 post 'archive'
93 96 post 'unarchive'
94 97 post 'close'
95 98 post 'reopen'
96 99 match 'copy', :via => [:get, :post]
97 100 end
98 101
99 102 resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
100 103 collection do
101 104 get 'autocomplete'
102 105 end
103 106 end
104 107
105 108 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
106 109
107 110 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
108 111 resources :issues, :only => [:index, :new, :create] do
109 112 resources :time_entries, :controller => 'timelog' do
110 113 collection do
111 114 get 'report'
112 115 end
113 116 end
114 117 end
115 118 # issue form update
116 119 match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form'
117 120
118 121 resources :files, :only => [:index, :new, :create]
119 122
120 123 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
121 124 collection do
122 125 put 'close_completed'
123 126 end
124 127 end
125 128 get 'versions.:format', :to => 'versions#index'
126 129 get 'roadmap', :to => 'versions#index', :format => false
127 130 get 'versions', :to => 'versions#index'
128 131
129 132 resources :news, :except => [:show, :edit, :update, :destroy]
130 133 resources :time_entries, :controller => 'timelog' do
131 134 get 'report', :on => :collection
132 135 end
133 136 resources :queries, :only => [:new, :create]
134 137 resources :issue_categories, :shallow => true
135 138 resources :documents, :except => [:show, :edit, :update, :destroy]
136 139 resources :boards
137 140 resources :repositories, :shallow => true, :except => [:index, :show] do
138 141 member do
139 142 match 'committers', :via => [:get, :post]
140 143 end
141 144 end
142 145
143 146 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
144 147 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
145 148 member do
146 149 get 'rename'
147 150 post 'rename'
148 151 get 'history'
149 152 get 'diff'
150 153 match 'preview', :via => [:post, :put]
151 154 post 'protect'
152 155 post 'add_attachment'
153 156 end
154 157 collection do
155 158 get 'export'
156 159 get 'date_index'
157 160 end
158 161 end
159 162 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
160 163 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
161 164 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
162 165 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
163 166 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
164 167 end
165 168
166 169 resources :issues do
167 170 collection do
168 171 match 'bulk_edit', :via => [:get, :post]
169 172 post 'bulk_update'
170 173 end
171 174 resources :time_entries, :controller => 'timelog' do
172 175 collection do
173 176 get 'report'
174 177 end
175 178 end
176 179 resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
177 180 end
178 181 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
179 182
180 183 resources :queries, :except => [:show]
181 184
182 185 resources :news, :only => [:index, :show, :edit, :update, :destroy]
183 186 match '/news/:id/comments', :to => 'comments#create', :via => :post
184 187 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
185 188
186 189 resources :versions, :only => [:show, :edit, :update, :destroy] do
187 190 post 'status_by', :on => :member
188 191 end
189 192
190 193 resources :documents, :only => [:show, :edit, :update, :destroy] do
191 194 post 'add_attachment', :on => :member
192 195 end
193 196
194 197 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
195 198
196 199 resources :time_entries, :controller => 'timelog', :except => :destroy do
197 200 collection do
198 201 get 'report'
199 202 get 'bulk_edit'
200 203 post 'bulk_update'
201 204 end
202 205 end
203 206 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
204 207 # TODO: delete /time_entries for bulk deletion
205 208 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
206 209
207 210 get 'projects/:id/activity', :to => 'activities#index'
208 211 get 'projects/:id/activity.:format', :to => 'activities#index'
209 212 get 'activity', :to => 'activities#index'
210 213
211 214 # repositories routes
212 215 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
213 216 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
214 217
215 218 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
216 219 :to => 'repositories#changes'
217 220
218 221 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
219 222 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
220 223 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
221 224 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
222 225 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
223 226 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
224 227 :controller => 'repositories',
225 228 :format => false,
226 229 :constraints => {
227 230 :action => /(browse|show|entry|raw|annotate|diff)/,
228 231 :rev => /[a-z0-9\.\-_]+/
229 232 }
230 233
231 234 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
232 235 get 'projects/:id/repository/graph', :to => 'repositories#graph'
233 236
234 237 get 'projects/:id/repository/changes(/*path(.:ext))',
235 238 :to => 'repositories#changes'
236 239
237 240 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
238 241 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
239 242 get 'projects/:id/repository/revision', :to => 'repositories#revision'
240 243 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
241 244 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
242 245 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
243 246 :controller => 'repositories',
244 247 :format => false,
245 248 :constraints => {
246 249 :action => /(browse|show|entry|raw|annotate|diff)/,
247 250 :rev => /[a-z0-9\.\-_]+/
248 251 }
249 252 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
250 253 :controller => 'repositories',
251 254 :action => /(browse|show|entry|raw|changes|annotate|diff)/
252 255 get 'projects/:id/repository/:action(/*path(.:ext))',
253 256 :controller => 'repositories',
254 257 :action => /(browse|show|entry|raw|changes|annotate|diff)/
255 258
256 259 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
257 260 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
258 261
259 262 # additional routes for having the file name at the end of url
260 263 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
261 264 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
262 265 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
263 266 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
264 267 resources :attachments, :only => [:show, :destroy]
265 268
266 269 resources :groups do
267 270 member do
268 271 get 'autocomplete_for_user'
269 272 end
270 273 end
271 274
272 275 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
273 276 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
274 277 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
275 278 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
276 279
277 280 resources :trackers, :except => :show do
278 281 collection do
279 282 match 'fields', :via => [:get, :post]
280 283 end
281 284 end
282 285 resources :issue_statuses, :except => :show do
283 286 collection do
284 287 post 'update_issue_done_ratio'
285 288 end
286 289 end
287 290 resources :custom_fields, :except => :show
288 291 resources :roles do
289 292 collection do
290 293 match 'permissions', :via => [:get, :post]
291 294 end
292 295 end
293 296 resources :enumerations, :except => :show
294 297 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
295 298
296 299 get 'projects/:id/search', :controller => 'search', :action => 'index'
297 300 get 'search', :controller => 'search', :action => 'index'
298 301
299 302 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
300 303
301 304 match 'admin', :controller => 'admin', :action => 'index', :via => :get
302 305 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
303 306 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
304 307 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
305 308 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
306 309 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
307 310
308 311 resources :auth_sources do
309 312 member do
310 313 get 'test_connection', :as => 'try_connection'
311 314 end
312 315 collection do
313 316 get 'autocomplete_for_new_user'
314 317 end
315 318 end
316 319
317 320 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
318 321 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
319 322 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
320 323 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
321 324 match 'settings', :controller => 'settings', :action => 'index', :via => :get
322 325 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
323 326 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
324 327
325 328 match 'sys/projects', :to => 'sys#projects', :via => :get
326 329 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
327 330 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get
328 331
329 332 match 'uploads', :to => 'attachments#upload', :via => :post
330 333
331 334 get 'robots.txt', :to => 'welcome#robots'
332 335
333 336 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
334 337 file = File.join(plugin_dir, "config/routes.rb")
335 338 if File.exists?(file)
336 339 begin
337 340 instance_eval File.read(file)
338 341 rescue Exception => e
339 342 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
340 343 exit 1
341 344 end
342 345 end
343 346 end
344 347 end
@@ -1,795 +1,846
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
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 :issue_relations,
29 29 :versions,
30 30 :trackers,
31 31 :projects_trackers,
32 32 :issue_categories,
33 33 :enabled_modules,
34 34 :enumerations,
35 35 :attachments,
36 36 :workflows,
37 37 :custom_fields,
38 38 :custom_values,
39 39 :custom_fields_projects,
40 40 :custom_fields_trackers,
41 41 :time_entries,
42 42 :journals,
43 43 :journal_details,
44 44 :queries,
45 45 :attachments
46 46
47 47 def setup
48 48 Setting.rest_api_enabled = '1'
49 49 end
50 50
51 51 context "/issues" do
52 52 # Use a private project to make sure auth is really working and not just
53 53 # only showing public issues.
54 54 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
55 55
56 56 should "contain metadata" do
57 57 get '/issues.xml'
58 58
59 59 assert_tag :tag => 'issues',
60 60 :attributes => {
61 61 :type => 'array',
62 62 :total_count => assigns(:issue_count),
63 63 :limit => 25,
64 64 :offset => 0
65 65 }
66 66 end
67 67
68 68 context "with offset and limit" do
69 69 should "use the params" do
70 70 get '/issues.xml?offset=2&limit=3'
71 71
72 72 assert_equal 3, assigns(:limit)
73 73 assert_equal 2, assigns(:offset)
74 74 assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}}
75 75 end
76 76 end
77 77
78 78 context "with nometa param" do
79 79 should "not contain metadata" do
80 80 get '/issues.xml?nometa=1'
81 81
82 82 assert_tag :tag => 'issues',
83 83 :attributes => {
84 84 :type => 'array',
85 85 :total_count => nil,
86 86 :limit => nil,
87 87 :offset => nil
88 88 }
89 89 end
90 90 end
91 91
92 92 context "with nometa header" do
93 93 should "not contain metadata" do
94 94 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
95 95
96 96 assert_tag :tag => 'issues',
97 97 :attributes => {
98 98 :type => 'array',
99 99 :total_count => nil,
100 100 :limit => nil,
101 101 :offset => nil
102 102 }
103 103 end
104 104 end
105 105
106 106 context "with relations" do
107 107 should "display relations" do
108 108 get '/issues.xml?include=relations'
109 109
110 110 assert_response :success
111 111 assert_equal 'application/xml', @response.content_type
112 112 assert_tag 'relations',
113 113 :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}},
114 114 :children => {:count => 1},
115 115 :child => {
116 116 :tag => 'relation',
117 117 :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3',
118 118 :relation_type => 'relates'}
119 119 }
120 120 assert_tag 'relations',
121 121 :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}},
122 122 :children => {:count => 0}
123 123 end
124 124 end
125 125
126 126 context "with invalid query params" do
127 127 should "return errors" do
128 128 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
129 129
130 130 assert_response :unprocessable_entity
131 131 assert_equal 'application/xml', @response.content_type
132 132 assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"}
133 133 end
134 134 end
135 135
136 136 context "with custom field filter" do
137 137 should "show only issues with the custom field value" do
138 138 get '/issues.xml',
139 139 {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='},
140 140 :v => {:cf_1 => ['MySQL']}}
141 141 expected_ids = Issue.visible.all(
142 142 :include => :custom_values,
143 143 :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id)
144 144 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
145 145 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
146 146 end
147 147 end
148 148 end
149 149
150 150 context "with custom field filter (shorthand method)" do
151 151 should "show only issues with the custom field value" do
152 152 get '/issues.xml', { :cf_1 => 'MySQL' }
153 153
154 154 expected_ids = Issue.visible.all(
155 155 :include => :custom_values,
156 156 :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id)
157 157
158 158 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
159 159 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
160 160 end
161 161 end
162 162 end
163 163 end
164 164
165 165 context "/index.json" do
166 166 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
167 167 end
168 168
169 169 context "/index.xml with filter" do
170 170 should "show only issues with the status_id" do
171 171 get '/issues.xml?status_id=5'
172 172
173 173 expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id)
174 174
175 175 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
176 176 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
177 177 end
178 178 end
179 179 end
180 180
181 181 context "/index.json with filter" do
182 182 should "show only issues with the status_id" do
183 183 get '/issues.json?status_id=5'
184 184
185 185 json = ActiveSupport::JSON.decode(response.body)
186 186 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
187 187 assert_equal 3, status_ids_used.length
188 188 assert status_ids_used.all? {|id| id == 5 }
189 189 end
190 190
191 191 end
192 192
193 193 # Issue 6 is on a private project
194 194 context "/issues/6.xml" do
195 195 should_allow_api_authentication(:get, "/issues/6.xml")
196 196 end
197 197
198 198 context "/issues/6.json" do
199 199 should_allow_api_authentication(:get, "/issues/6.json")
200 200 end
201 201
202 202 context "GET /issues/:id" do
203 203 context "with journals" do
204 204 context ".xml" do
205 205 should "display journals" do
206 206 get '/issues/1.xml?include=journals'
207 207
208 208 assert_tag :tag => 'issue',
209 209 :child => {
210 210 :tag => 'journals',
211 211 :attributes => { :type => 'array' },
212 212 :child => {
213 213 :tag => 'journal',
214 214 :attributes => { :id => '1'},
215 215 :child => {
216 216 :tag => 'details',
217 217 :attributes => { :type => 'array' },
218 218 :child => {
219 219 :tag => 'detail',
220 220 :attributes => { :name => 'status_id' },
221 221 :child => {
222 222 :tag => 'old_value',
223 223 :content => '1',
224 224 :sibling => {
225 225 :tag => 'new_value',
226 226 :content => '2'
227 227 }
228 228 }
229 229 }
230 230 }
231 231 }
232 232 }
233 233 end
234 234 end
235 235 end
236 236
237 237 context "with custom fields" do
238 238 context ".xml" do
239 239 should "display custom fields" do
240 240 get '/issues/3.xml'
241 241
242 242 assert_tag :tag => 'issue',
243 243 :child => {
244 244 :tag => 'custom_fields',
245 245 :attributes => { :type => 'array' },
246 246 :child => {
247 247 :tag => 'custom_field',
248 248 :attributes => { :id => '1'},
249 249 :child => {
250 250 :tag => 'value',
251 251 :content => 'MySQL'
252 252 }
253 253 }
254 254 }
255 255
256 256 assert_nothing_raised do
257 257 Hash.from_xml(response.body).to_xml
258 258 end
259 259 end
260 260 end
261 261 end
262 262
263 263 context "with multi custom fields" do
264 264 setup do
265 265 field = CustomField.find(1)
266 266 field.update_attribute :multiple, true
267 267 issue = Issue.find(3)
268 268 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
269 269 issue.save!
270 270 end
271 271
272 272 context ".xml" do
273 273 should "display custom fields" do
274 274 get '/issues/3.xml'
275 275 assert_response :success
276 276 assert_tag :tag => 'issue',
277 277 :child => {
278 278 :tag => 'custom_fields',
279 279 :attributes => { :type => 'array' },
280 280 :child => {
281 281 :tag => 'custom_field',
282 282 :attributes => { :id => '1'},
283 283 :child => {
284 284 :tag => 'value',
285 285 :attributes => { :type => 'array' },
286 286 :children => { :count => 2 }
287 287 }
288 288 }
289 289 }
290 290
291 291 xml = Hash.from_xml(response.body)
292 292 custom_fields = xml['issue']['custom_fields']
293 293 assert_kind_of Array, custom_fields
294 294 field = custom_fields.detect {|f| f['id'] == '1'}
295 295 assert_kind_of Hash, field
296 296 assert_equal ['MySQL', 'Oracle'], field['value'].sort
297 297 end
298 298 end
299 299
300 300 context ".json" do
301 301 should "display custom fields" do
302 302 get '/issues/3.json'
303 303 assert_response :success
304 304 json = ActiveSupport::JSON.decode(response.body)
305 305 custom_fields = json['issue']['custom_fields']
306 306 assert_kind_of Array, custom_fields
307 307 field = custom_fields.detect {|f| f['id'] == 1}
308 308 assert_kind_of Hash, field
309 309 assert_equal ['MySQL', 'Oracle'], field['value'].sort
310 310 end
311 311 end
312 312 end
313 313
314 314 context "with empty value for multi custom field" do
315 315 setup do
316 316 field = CustomField.find(1)
317 317 field.update_attribute :multiple, true
318 318 issue = Issue.find(3)
319 319 issue.custom_field_values = {1 => ['']}
320 320 issue.save!
321 321 end
322 322
323 323 context ".xml" do
324 324 should "display custom fields" do
325 325 get '/issues/3.xml'
326 326 assert_response :success
327 327 assert_tag :tag => 'issue',
328 328 :child => {
329 329 :tag => 'custom_fields',
330 330 :attributes => { :type => 'array' },
331 331 :child => {
332 332 :tag => 'custom_field',
333 333 :attributes => { :id => '1'},
334 334 :child => {
335 335 :tag => 'value',
336 336 :attributes => { :type => 'array' },
337 337 :children => { :count => 0 }
338 338 }
339 339 }
340 340 }
341 341
342 342 xml = Hash.from_xml(response.body)
343 343 custom_fields = xml['issue']['custom_fields']
344 344 assert_kind_of Array, custom_fields
345 345 field = custom_fields.detect {|f| f['id'] == '1'}
346 346 assert_kind_of Hash, field
347 347 assert_equal [], field['value']
348 348 end
349 349 end
350 350
351 351 context ".json" do
352 352 should "display custom fields" do
353 353 get '/issues/3.json'
354 354 assert_response :success
355 355 json = ActiveSupport::JSON.decode(response.body)
356 356 custom_fields = json['issue']['custom_fields']
357 357 assert_kind_of Array, custom_fields
358 358 field = custom_fields.detect {|f| f['id'] == 1}
359 359 assert_kind_of Hash, field
360 360 assert_equal [], field['value'].sort
361 361 end
362 362 end
363 363 end
364 364
365 365 context "with attachments" do
366 366 context ".xml" do
367 367 should "display attachments" do
368 368 get '/issues/3.xml?include=attachments'
369 369
370 370 assert_tag :tag => 'issue',
371 371 :child => {
372 372 :tag => 'attachments',
373 373 :children => {:count => 5},
374 374 :child => {
375 375 :tag => 'attachment',
376 376 :child => {
377 377 :tag => 'filename',
378 378 :content => 'source.rb',
379 379 :sibling => {
380 380 :tag => 'content_url',
381 381 :content => 'http://www.example.com/attachments/download/4/source.rb'
382 382 }
383 383 }
384 384 }
385 385 }
386 386 end
387 387 end
388 388 end
389 389
390 390 context "with subtasks" do
391 391 setup do
392 392 @c1 = Issue.create!(
393 393 :status_id => 1, :subject => "child c1",
394 394 :tracker_id => 1, :project_id => 1, :author_id => 1,
395 395 :parent_issue_id => 1
396 396 )
397 397 @c2 = Issue.create!(
398 398 :status_id => 1, :subject => "child c2",
399 399 :tracker_id => 1, :project_id => 1, :author_id => 1,
400 400 :parent_issue_id => 1
401 401 )
402 402 @c3 = Issue.create!(
403 403 :status_id => 1, :subject => "child c3",
404 404 :tracker_id => 1, :project_id => 1, :author_id => 1,
405 405 :parent_issue_id => @c1.id
406 406 )
407 407 end
408 408
409 409 context ".xml" do
410 410 should "display children" do
411 411 get '/issues/1.xml?include=children'
412 412
413 413 assert_tag :tag => 'issue',
414 414 :child => {
415 415 :tag => 'children',
416 416 :children => {:count => 2},
417 417 :child => {
418 418 :tag => 'issue',
419 419 :attributes => {:id => @c1.id.to_s},
420 420 :child => {
421 421 :tag => 'subject',
422 422 :content => 'child c1',
423 423 :sibling => {
424 424 :tag => 'children',
425 425 :children => {:count => 1},
426 426 :child => {
427 427 :tag => 'issue',
428 428 :attributes => {:id => @c3.id.to_s}
429 429 }
430 430 }
431 431 }
432 432 }
433 433 }
434 434 end
435 435
436 436 context ".json" do
437 437 should "display children" do
438 438 get '/issues/1.json?include=children'
439 439
440 440 json = ActiveSupport::JSON.decode(response.body)
441 441 assert_equal([
442 442 {
443 443 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'},
444 444 'children' => [{'id' => @c3.id, 'subject' => 'child c3',
445 445 'tracker' => {'id' => 1, 'name' => 'Bug'} }]
446 446 },
447 447 { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} }
448 448 ],
449 449 json['issue']['children'])
450 450 end
451 451 end
452 452 end
453 453 end
454 454 end
455 455
456 test "GET /issues/:id.xml?include=watchers should include watchers" do
457 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
458
459 get '/issues/1.xml?include=watchers', {}, credentials('jsmith')
460
461 assert_response :ok
462 assert_equal 'application/xml', response.content_type
463 assert_select 'issue' do
464 assert_select 'watchers', Issue.find(1).watchers.count
465 assert_select 'watchers' do
466 assert_select 'user[id=3]'
467 end
468 end
469 end
470
456 471 context "POST /issues.xml" do
457 472 should_allow_api_authentication(
458 473 :post,
459 474 '/issues.xml',
460 475 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
461 476 {:success_code => :created}
462 477 )
463 478 should "create an issue with the attributes" do
464 479 assert_difference('Issue.count') do
465 480 post '/issues.xml',
466 481 {:issue => {:project_id => 1, :subject => 'API test',
467 482 :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
468 483 end
469 484 issue = Issue.first(:order => 'id DESC')
470 485 assert_equal 1, issue.project_id
471 486 assert_equal 2, issue.tracker_id
472 487 assert_equal 3, issue.status_id
473 488 assert_equal 'API test', issue.subject
474 489
475 490 assert_response :created
476 491 assert_equal 'application/xml', @response.content_type
477 492 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
478 493 end
479 494 end
480 495
496 test "POST /issues.xml with watcher_user_ids should create issue with watchers" do
497 assert_difference('Issue.count') do
498 post '/issues.xml',
499 {:issue => {:project_id => 1, :subject => 'Watchers',
500 :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith')
501 assert_response :created
502 end
503 issue = Issue.order('id desc').first
504 assert_equal 2, issue.watchers.size
505 assert_equal [1, 3], issue.watcher_user_ids.sort
506 end
507
481 508 context "POST /issues.xml with failure" do
482 509 should "have an errors tag" do
483 510 assert_no_difference('Issue.count') do
484 511 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
485 512 end
486 513
487 514 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
488 515 end
489 516 end
490 517
491 518 context "POST /issues.json" do
492 519 should_allow_api_authentication(:post,
493 520 '/issues.json',
494 521 {:issue => {:project_id => 1, :subject => 'API test',
495 522 :tracker_id => 2, :status_id => 3}},
496 523 {:success_code => :created})
497 524
498 525 should "create an issue with the attributes" do
499 526 assert_difference('Issue.count') do
500 527 post '/issues.json',
501 528 {:issue => {:project_id => 1, :subject => 'API test',
502 529 :tracker_id => 2, :status_id => 3}},
503 530 credentials('jsmith')
504 531 end
505 532
506 533 issue = Issue.first(:order => 'id DESC')
507 534 assert_equal 1, issue.project_id
508 535 assert_equal 2, issue.tracker_id
509 536 assert_equal 3, issue.status_id
510 537 assert_equal 'API test', issue.subject
511 538 end
512 539
513 540 end
514 541
515 542 context "POST /issues.json with failure" do
516 543 should "have an errors element" do
517 544 assert_no_difference('Issue.count') do
518 545 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
519 546 end
520 547
521 548 json = ActiveSupport::JSON.decode(response.body)
522 549 assert json['errors'].include?("Subject can't be blank")
523 550 end
524 551 end
525 552
526 553 # Issue 6 is on a private project
527 554 context "PUT /issues/6.xml" do
528 555 setup do
529 556 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
530 557 end
531 558
532 559 should_allow_api_authentication(:put,
533 560 '/issues/6.xml',
534 561 {:issue => {:subject => 'API update', :notes => 'A new note'}},
535 562 {:success_code => :ok})
536 563
537 564 should "not create a new issue" do
538 565 assert_no_difference('Issue.count') do
539 566 put '/issues/6.xml', @parameters, credentials('jsmith')
540 567 end
541 568 end
542 569
543 570 should "create a new journal" do
544 571 assert_difference('Journal.count') do
545 572 put '/issues/6.xml', @parameters, credentials('jsmith')
546 573 end
547 574 end
548 575
549 576 should "add the note to the journal" do
550 577 put '/issues/6.xml', @parameters, credentials('jsmith')
551 578
552 579 journal = Journal.last
553 580 assert_equal "A new note", journal.notes
554 581 end
555 582
556 583 should "update the issue" do
557 584 put '/issues/6.xml', @parameters, credentials('jsmith')
558 585
559 586 issue = Issue.find(6)
560 587 assert_equal "API update", issue.subject
561 588 end
562 589
563 590 end
564 591
565 592 context "PUT /issues/3.xml with custom fields" do
566 593 setup do
567 594 @parameters = {
568 595 :issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' },
569 596 {'id' => '2', 'value' => '150'}]}
570 597 }
571 598 end
572 599
573 600 should "update custom fields" do
574 601 assert_no_difference('Issue.count') do
575 602 put '/issues/3.xml', @parameters, credentials('jsmith')
576 603 end
577 604
578 605 issue = Issue.find(3)
579 606 assert_equal '150', issue.custom_value_for(2).value
580 607 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
581 608 end
582 609 end
583 610
584 611 context "PUT /issues/3.xml with multi custom fields" do
585 612 setup do
586 613 field = CustomField.find(1)
587 614 field.update_attribute :multiple, true
588 615 @parameters = {
589 616 :issue => {:custom_fields => [{'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] },
590 617 {'id' => '2', 'value' => '150'}]}
591 618 }
592 619 end
593 620
594 621 should "update custom fields" do
595 622 assert_no_difference('Issue.count') do
596 623 put '/issues/3.xml', @parameters, credentials('jsmith')
597 624 end
598 625
599 626 issue = Issue.find(3)
600 627 assert_equal '150', issue.custom_value_for(2).value
601 628 assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort
602 629 end
603 630 end
604 631
605 632 context "PUT /issues/3.xml with project change" do
606 633 setup do
607 634 @parameters = {:issue => {:project_id => 2, :subject => 'Project changed'}}
608 635 end
609 636
610 637 should "update project" do
611 638 assert_no_difference('Issue.count') do
612 639 put '/issues/3.xml', @parameters, credentials('jsmith')
613 640 end
614 641
615 642 issue = Issue.find(3)
616 643 assert_equal 2, issue.project_id
617 644 assert_equal 'Project changed', issue.subject
618 645 end
619 646 end
620 647
621 648 context "PUT /issues/6.xml with failed update" do
622 649 setup do
623 650 @parameters = {:issue => {:subject => ''}}
624 651 end
625 652
626 653 should "not create a new issue" do
627 654 assert_no_difference('Issue.count') do
628 655 put '/issues/6.xml', @parameters, credentials('jsmith')
629 656 end
630 657 end
631 658
632 659 should "not create a new journal" do
633 660 assert_no_difference('Journal.count') do
634 661 put '/issues/6.xml', @parameters, credentials('jsmith')
635 662 end
636 663 end
637 664
638 665 should "have an errors tag" do
639 666 put '/issues/6.xml', @parameters, credentials('jsmith')
640 667
641 668 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
642 669 end
643 670 end
644 671
645 672 context "PUT /issues/6.json" do
646 673 setup do
647 674 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
648 675 end
649 676
650 677 should_allow_api_authentication(:put,
651 678 '/issues/6.json',
652 679 {:issue => {:subject => 'API update', :notes => 'A new note'}},
653 680 {:success_code => :ok})
654 681
655 682 should "update the issue" do
656 683 assert_no_difference('Issue.count') do
657 684 assert_difference('Journal.count') do
658 685 put '/issues/6.json', @parameters, credentials('jsmith')
659 686
660 687 assert_response :ok
661 688 assert_equal '', response.body
662 689 end
663 690 end
664 691
665 692 issue = Issue.find(6)
666 693 assert_equal "API update", issue.subject
667 694 journal = Journal.last
668 695 assert_equal "A new note", journal.notes
669 696 end
670 697 end
671 698
672 699 context "PUT /issues/6.json with failed update" do
673 700 should "return errors" do
674 701 assert_no_difference('Issue.count') do
675 702 assert_no_difference('Journal.count') do
676 703 put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith')
677 704
678 705 assert_response :unprocessable_entity
679 706 end
680 707 end
681 708
682 709 json = ActiveSupport::JSON.decode(response.body)
683 710 assert json['errors'].include?("Subject can't be blank")
684 711 end
685 712 end
686 713
687 714 context "DELETE /issues/1.xml" do
688 715 should_allow_api_authentication(:delete,
689 716 '/issues/6.xml',
690 717 {},
691 718 {:success_code => :ok})
692 719
693 720 should "delete the issue" do
694 721 assert_difference('Issue.count', -1) do
695 722 delete '/issues/6.xml', {}, credentials('jsmith')
696 723
697 724 assert_response :ok
698 725 assert_equal '', response.body
699 726 end
700 727
701 728 assert_nil Issue.find_by_id(6)
702 729 end
703 730 end
704 731
705 732 context "DELETE /issues/1.json" do
706 733 should_allow_api_authentication(:delete,
707 734 '/issues/6.json',
708 735 {},
709 736 {:success_code => :ok})
710 737
711 738 should "delete the issue" do
712 739 assert_difference('Issue.count', -1) do
713 740 delete '/issues/6.json', {}, credentials('jsmith')
714 741
715 742 assert_response :ok
716 743 assert_equal '', response.body
717 744 end
718 745
719 746 assert_nil Issue.find_by_id(6)
720 747 end
721 748 end
722 749
750 test "POST /issues/:id/watchers.xml should add watcher" do
751 assert_difference 'Watcher.count' do
752 post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith')
753
754 assert_response :ok
755 assert_equal '', response.body
756 end
757 watcher = Watcher.order('id desc').first
758 assert_equal Issue.find(1), watcher.watchable
759 assert_equal User.find(3), watcher.user
760 end
761
762 test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do
763 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
764
765 assert_difference 'Watcher.count', -1 do
766 delete '/issues/1/watchers/3.xml', {}, credentials('jsmith')
767
768 assert_response :ok
769 assert_equal '', response.body
770 end
771 assert_equal false, Issue.find(1).watched_by?(User.find(3))
772 end
773
723 774 def test_create_issue_with_uploaded_file
724 775 set_tmp_attachments_directory
725 776 # upload the file
726 777 assert_difference 'Attachment.count' do
727 778 post '/uploads.xml', 'test_create_with_upload',
728 779 {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
729 780 assert_response :created
730 781 end
731 782 xml = Hash.from_xml(response.body)
732 783 token = xml['upload']['token']
733 784 attachment = Attachment.first(:order => 'id DESC')
734 785
735 786 # create the issue with the upload's token
736 787 assert_difference 'Issue.count' do
737 788 post '/issues.xml',
738 789 {:issue => {:project_id => 1, :subject => 'Uploaded file',
739 790 :uploads => [{:token => token, :filename => 'test.txt',
740 791 :content_type => 'text/plain'}]}},
741 792 credentials('jsmith')
742 793 assert_response :created
743 794 end
744 795 issue = Issue.first(:order => 'id DESC')
745 796 assert_equal 1, issue.attachments.count
746 797 assert_equal attachment, issue.attachments.first
747 798
748 799 attachment.reload
749 800 assert_equal 'test.txt', attachment.filename
750 801 assert_equal 'text/plain', attachment.content_type
751 802 assert_equal 'test_create_with_upload'.size, attachment.filesize
752 803 assert_equal 2, attachment.author_id
753 804
754 805 # get the issue with its attachments
755 806 get "/issues/#{issue.id}.xml", :include => 'attachments'
756 807 assert_response :success
757 808 xml = Hash.from_xml(response.body)
758 809 attachments = xml['issue']['attachments']
759 810 assert_kind_of Array, attachments
760 811 assert_equal 1, attachments.size
761 812 url = attachments.first['content_url']
762 813 assert_not_nil url
763 814
764 815 # download the attachment
765 816 get url
766 817 assert_response :success
767 818 end
768 819
769 820 def test_update_issue_with_uploaded_file
770 821 set_tmp_attachments_directory
771 822 # upload the file
772 823 assert_difference 'Attachment.count' do
773 824 post '/uploads.xml', 'test_upload_with_upload',
774 825 {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith'))
775 826 assert_response :created
776 827 end
777 828 xml = Hash.from_xml(response.body)
778 829 token = xml['upload']['token']
779 830 attachment = Attachment.first(:order => 'id DESC')
780 831
781 832 # update the issue with the upload's token
782 833 assert_difference 'Journal.count' do
783 834 put '/issues/1.xml',
784 835 {:issue => {:notes => 'Attachment added',
785 836 :uploads => [{:token => token, :filename => 'test.txt',
786 837 :content_type => 'text/plain'}]}},
787 838 credentials('jsmith')
788 839 assert_response :ok
789 840 assert_equal '', @response.body
790 841 end
791 842
792 843 issue = Issue.find(1)
793 844 assert_include attachment, issue.attachments
794 845 end
795 846 end
@@ -1,51 +1,61
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class RoutingWatchersTest < ActionController::IntegrationTest
21 21 def test_watchers
22 22 assert_routing(
23 23 { :method => 'get', :path => "/watchers/new" },
24 24 { :controller => 'watchers', :action => 'new' }
25 25 )
26 26 assert_routing(
27 27 { :method => 'post', :path => "/watchers/append" },
28 28 { :controller => 'watchers', :action => 'append' }
29 29 )
30 30 assert_routing(
31 31 { :method => 'post', :path => "/watchers" },
32 32 { :controller => 'watchers', :action => 'create' }
33 33 )
34 34 assert_routing(
35 35 { :method => 'post', :path => "/watchers/destroy" },
36 36 { :controller => 'watchers', :action => 'destroy' }
37 37 )
38 38 assert_routing(
39 39 { :method => 'get', :path => "/watchers/autocomplete_for_user" },
40 40 { :controller => 'watchers', :action => 'autocomplete_for_user' }
41 41 )
42 42 assert_routing(
43 43 { :method => 'post', :path => "/watchers/watch" },
44 44 { :controller => 'watchers', :action => 'watch' }
45 45 )
46 46 assert_routing(
47 47 { :method => 'post', :path => "/watchers/unwatch" },
48 48 { :controller => 'watchers', :action => 'unwatch' }
49 49 )
50 assert_routing(
51 { :method => 'post', :path => "/issues/12/watchers.xml" },
52 { :controller => 'watchers', :action => 'create',
53 :object_type => 'issue', :object_id => '12', :format => 'xml' }
54 )
55 assert_routing(
56 { :method => 'delete', :path => "/issues/12/watchers/3.xml" },
57 { :controller => 'watchers', :action => 'destroy',
58 :object_type => 'issue', :object_id => '12', :user_id => '3', :format => 'xml'}
59 )
50 60 end
51 61 end
General Comments 0
You need to be logged in to leave comments. Login now