##// END OF EJS Templates
Merged r15955 and r15956 (#24297)....
Jean-Philippe Lang -
r15617:6ebb700db2f5
parent child
Show More
@@ -1,274 +1,278
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20
20
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :check_editability, :only => [:edit, :update]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
24 before_filter :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
24
25
25 before_filter :find_optional_project, :only => [:new, :create, :index, :report]
26 before_filter :find_optional_project, :only => [:new, :create, :index, :report]
26 before_filter :authorize_global, :only => [:new, :create, :index, :report]
27 before_filter :authorize_global, :only => [:new, :create, :index, :report]
27
28
28 accept_rss_auth :index
29 accept_rss_auth :index
29 accept_api_auth :index, :show, :create, :update, :destroy
30 accept_api_auth :index, :show, :create, :update, :destroy
30
31
31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32
33
33 helper :sort
34 helper :sort
34 include SortHelper
35 include SortHelper
35 helper :issues
36 helper :issues
36 include TimelogHelper
37 include TimelogHelper
37 helper :custom_fields
38 helper :custom_fields
38 include CustomFieldsHelper
39 include CustomFieldsHelper
39 helper :queries
40 helper :queries
40 include QueriesHelper
41 include QueriesHelper
41
42
42 def index
43 def index
43 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
44 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
44
45
45 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
46 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
46 sort_update(@query.sortable_columns)
47 sort_update(@query.sortable_columns)
47 scope = time_entry_scope(:order => sort_clause).
48 scope = time_entry_scope(:order => sort_clause).
48 includes(:project, :user, :issue).
49 includes(:project, :user, :issue).
49 preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
50 preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
50
51
51 respond_to do |format|
52 respond_to do |format|
52 format.html {
53 format.html {
53 @entry_count = scope.count
54 @entry_count = scope.count
54 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
55 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
55 @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
56 @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
56 @total_hours = scope.sum(:hours).to_f
57 @total_hours = scope.sum(:hours).to_f
57
58
58 render :layout => !request.xhr?
59 render :layout => !request.xhr?
59 }
60 }
60 format.api {
61 format.api {
61 @entry_count = scope.count
62 @entry_count = scope.count
62 @offset, @limit = api_offset_and_limit
63 @offset, @limit = api_offset_and_limit
63 @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).to_a
64 @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).to_a
64 }
65 }
65 format.atom {
66 format.atom {
66 entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").to_a
67 entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").to_a
67 render_feed(entries, :title => l(:label_spent_time))
68 render_feed(entries, :title => l(:label_spent_time))
68 }
69 }
69 format.csv {
70 format.csv {
70 # Export all entries
71 # Export all entries
71 @entries = scope.to_a
72 @entries = scope.to_a
72 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
73 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
73 }
74 }
74 end
75 end
75 end
76 end
76
77
77 def report
78 def report
78 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
79 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
79 scope = time_entry_scope
80 scope = time_entry_scope
80
81
81 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
82 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
82
83
83 respond_to do |format|
84 respond_to do |format|
84 format.html { render :layout => !request.xhr? }
85 format.html { render :layout => !request.xhr? }
85 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
86 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
86 end
87 end
87 end
88 end
88
89
89 def show
90 def show
90 respond_to do |format|
91 respond_to do |format|
91 # TODO: Implement html response
92 # TODO: Implement html response
92 format.html { render :nothing => true, :status => 406 }
93 format.html { render :nothing => true, :status => 406 }
93 format.api
94 format.api
94 end
95 end
95 end
96 end
96
97
97 def new
98 def new
98 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
99 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
99 @time_entry.safe_attributes = params[:time_entry]
100 @time_entry.safe_attributes = params[:time_entry]
100 end
101 end
101
102
102 def create
103 def create
103 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
104 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
104 @time_entry.safe_attributes = params[:time_entry]
105 @time_entry.safe_attributes = params[:time_entry]
105 if @time_entry.project && !User.current.allowed_to?(:log_time, @time_entry.project)
106 if @time_entry.project && !User.current.allowed_to?(:log_time, @time_entry.project)
106 render_403
107 render_403
107 return
108 return
108 end
109 end
109
110
110 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
111 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
111
112
112 if @time_entry.save
113 if @time_entry.save
113 respond_to do |format|
114 respond_to do |format|
114 format.html {
115 format.html {
115 flash[:notice] = l(:notice_successful_create)
116 flash[:notice] = l(:notice_successful_create)
116 if params[:continue]
117 if params[:continue]
117 options = {
118 options = {
118 :time_entry => {
119 :time_entry => {
119 :project_id => params[:time_entry][:project_id],
120 :project_id => params[:time_entry][:project_id],
120 :issue_id => @time_entry.issue_id,
121 :issue_id => @time_entry.issue_id,
121 :activity_id => @time_entry.activity_id
122 :activity_id => @time_entry.activity_id
122 },
123 },
123 :back_url => params[:back_url]
124 :back_url => params[:back_url]
124 }
125 }
125 if params[:project_id] && @time_entry.project
126 if params[:project_id] && @time_entry.project
126 redirect_to new_project_time_entry_path(@time_entry.project, options)
127 redirect_to new_project_time_entry_path(@time_entry.project, options)
127 elsif params[:issue_id] && @time_entry.issue
128 elsif params[:issue_id] && @time_entry.issue
128 redirect_to new_issue_time_entry_path(@time_entry.issue, options)
129 redirect_to new_issue_time_entry_path(@time_entry.issue, options)
129 else
130 else
130 redirect_to new_time_entry_path(options)
131 redirect_to new_time_entry_path(options)
131 end
132 end
132 else
133 else
133 redirect_back_or_default project_time_entries_path(@time_entry.project)
134 redirect_back_or_default project_time_entries_path(@time_entry.project)
134 end
135 end
135 }
136 }
136 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
137 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
137 end
138 end
138 else
139 else
139 respond_to do |format|
140 respond_to do |format|
140 format.html { render :action => 'new' }
141 format.html { render :action => 'new' }
141 format.api { render_validation_errors(@time_entry) }
142 format.api { render_validation_errors(@time_entry) }
142 end
143 end
143 end
144 end
144 end
145 end
145
146
146 def edit
147 def edit
147 @time_entry.safe_attributes = params[:time_entry]
148 @time_entry.safe_attributes = params[:time_entry]
148 end
149 end
149
150
150 def update
151 def update
151 @time_entry.safe_attributes = params[:time_entry]
152 @time_entry.safe_attributes = params[:time_entry]
152
153
153 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
154 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
154
155
155 if @time_entry.save
156 if @time_entry.save
156 respond_to do |format|
157 respond_to do |format|
157 format.html {
158 format.html {
158 flash[:notice] = l(:notice_successful_update)
159 flash[:notice] = l(:notice_successful_update)
159 redirect_back_or_default project_time_entries_path(@time_entry.project)
160 redirect_back_or_default project_time_entries_path(@time_entry.project)
160 }
161 }
161 format.api { render_api_ok }
162 format.api { render_api_ok }
162 end
163 end
163 else
164 else
164 respond_to do |format|
165 respond_to do |format|
165 format.html { render :action => 'edit' }
166 format.html { render :action => 'edit' }
166 format.api { render_validation_errors(@time_entry) }
167 format.api { render_validation_errors(@time_entry) }
167 end
168 end
168 end
169 end
169 end
170 end
170
171
171 def bulk_edit
172 def bulk_edit
172 @available_activities = TimeEntryActivity.shared.active
173 @available_activities = TimeEntryActivity.shared.active
173 @custom_fields = TimeEntry.first.available_custom_fields
174 @custom_fields = TimeEntry.first.available_custom_fields
174 end
175 end
175
176
176 def bulk_update
177 def bulk_update
177 attributes = parse_params_for_bulk_update(params[:time_entry])
178 attributes = parse_params_for_bulk_update(params[:time_entry])
178
179
179 unsaved_time_entry_ids = []
180 unsaved_time_entry_ids = []
180 @time_entries.each do |time_entry|
181 @time_entries.each do |time_entry|
181 time_entry.reload
182 time_entry.reload
182 time_entry.safe_attributes = attributes
183 time_entry.safe_attributes = attributes
183 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
184 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
184 unless time_entry.save
185 unless time_entry.save
185 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info?
186 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info?
186 # Keep unsaved time_entry ids to display them in flash error
187 # Keep unsaved time_entry ids to display them in flash error
187 unsaved_time_entry_ids << time_entry.id
188 unsaved_time_entry_ids << time_entry.id
188 end
189 end
189 end
190 end
190 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
191 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
191 redirect_back_or_default project_time_entries_path(@projects.first)
192 redirect_back_or_default project_time_entries_path(@projects.first)
192 end
193 end
193
194
194 def destroy
195 def destroy
195 destroyed = TimeEntry.transaction do
196 destroyed = TimeEntry.transaction do
196 @time_entries.each do |t|
197 @time_entries.each do |t|
197 unless t.destroy && t.destroyed?
198 unless t.destroy && t.destroyed?
198 raise ActiveRecord::Rollback
199 raise ActiveRecord::Rollback
199 end
200 end
200 end
201 end
201 end
202 end
202
203
203 respond_to do |format|
204 respond_to do |format|
204 format.html {
205 format.html {
205 if destroyed
206 if destroyed
206 flash[:notice] = l(:notice_successful_delete)
207 flash[:notice] = l(:notice_successful_delete)
207 else
208 else
208 flash[:error] = l(:notice_unable_delete_time_entry)
209 flash[:error] = l(:notice_unable_delete_time_entry)
209 end
210 end
210 redirect_back_or_default project_time_entries_path(@projects.first)
211 redirect_back_or_default project_time_entries_path(@projects.first)
211 }
212 }
212 format.api {
213 format.api {
213 if destroyed
214 if destroyed
214 render_api_ok
215 render_api_ok
215 else
216 else
216 render_validation_errors(@time_entries)
217 render_validation_errors(@time_entries)
217 end
218 end
218 }
219 }
219 end
220 end
220 end
221 end
221
222
222 private
223 private
223 def find_time_entry
224 def find_time_entry
224 @time_entry = TimeEntry.find(params[:id])
225 @time_entry = TimeEntry.find(params[:id])
226 @project = @time_entry.project
227 rescue ActiveRecord::RecordNotFound
228 render_404
229 end
230
231 def check_editability
225 unless @time_entry.editable_by?(User.current)
232 unless @time_entry.editable_by?(User.current)
226 render_403
233 render_403
227 return false
234 return false
228 end
235 end
229 @project = @time_entry.project
230 rescue ActiveRecord::RecordNotFound
231 render_404
232 end
236 end
233
237
234 def find_time_entries
238 def find_time_entries
235 @time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).to_a
239 @time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).to_a
236 raise ActiveRecord::RecordNotFound if @time_entries.empty?
240 raise ActiveRecord::RecordNotFound if @time_entries.empty?
237 raise Unauthorized unless @time_entries.all? {|t| t.editable_by?(User.current)}
241 raise Unauthorized unless @time_entries.all? {|t| t.editable_by?(User.current)}
238 @projects = @time_entries.collect(&:project).compact.uniq
242 @projects = @time_entries.collect(&:project).compact.uniq
239 @project = @projects.first if @projects.size == 1
243 @project = @projects.first if @projects.size == 1
240 rescue ActiveRecord::RecordNotFound
244 rescue ActiveRecord::RecordNotFound
241 render_404
245 render_404
242 end
246 end
243
247
244 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
248 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
245 if unsaved_time_entry_ids.empty?
249 if unsaved_time_entry_ids.empty?
246 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
250 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
247 else
251 else
248 flash[:error] = l(:notice_failed_to_save_time_entries,
252 flash[:error] = l(:notice_failed_to_save_time_entries,
249 :count => unsaved_time_entry_ids.size,
253 :count => unsaved_time_entry_ids.size,
250 :total => time_entries.size,
254 :total => time_entries.size,
251 :ids => '#' + unsaved_time_entry_ids.join(', #'))
255 :ids => '#' + unsaved_time_entry_ids.join(', #'))
252 end
256 end
253 end
257 end
254
258
255 def find_optional_project
259 def find_optional_project
256 if params[:issue_id].present?
260 if params[:issue_id].present?
257 @issue = Issue.find(params[:issue_id])
261 @issue = Issue.find(params[:issue_id])
258 @project = @issue.project
262 @project = @issue.project
259 elsif params[:project_id].present?
263 elsif params[:project_id].present?
260 @project = Project.find(params[:project_id])
264 @project = Project.find(params[:project_id])
261 end
265 end
262 rescue ActiveRecord::RecordNotFound
266 rescue ActiveRecord::RecordNotFound
263 render_404
267 render_404
264 end
268 end
265
269
266 # Returns the TimeEntry scope for index and report actions
270 # Returns the TimeEntry scope for index and report actions
267 def time_entry_scope(options={})
271 def time_entry_scope(options={})
268 scope = @query.results_scope(options)
272 scope = @query.results_scope(options)
269 if @issue
273 if @issue
270 scope = scope.on_issue(@issue)
274 scope = scope.on_issue(@issue)
271 end
275 end
272 scope
276 scope
273 end
277 end
274 end
278 end
@@ -1,135 +1,146
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class Redmine::ApiTest::TimeEntriesTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::TimeEntriesTest < Redmine::ApiTest::Base
21 fixtures :projects, :trackers, :issue_statuses, :issues,
21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 :enumerations, :users, :issue_categories,
22 :enumerations, :users, :issue_categories,
23 :projects_trackers,
23 :projects_trackers,
24 :roles,
24 :roles,
25 :member_roles,
25 :member_roles,
26 :members,
26 :members,
27 :enabled_modules,
27 :enabled_modules,
28 :time_entries
28 :time_entries
29
29
30 test "GET /time_entries.xml should return time entries" do
30 test "GET /time_entries.xml should return time entries" do
31 get '/time_entries.xml', {}, credentials('jsmith')
31 get '/time_entries.xml', {}, credentials('jsmith')
32 assert_response :success
32 assert_response :success
33 assert_equal 'application/xml', @response.content_type
33 assert_equal 'application/xml', @response.content_type
34 assert_select 'time_entries[type=array] time_entry id', :text => '2'
34 assert_select 'time_entries[type=array] time_entry id', :text => '2'
35 end
35 end
36
36
37 test "GET /time_entries.xml with limit should return limited results" do
37 test "GET /time_entries.xml with limit should return limited results" do
38 get '/time_entries.xml?limit=2', {}, credentials('jsmith')
38 get '/time_entries.xml?limit=2', {}, credentials('jsmith')
39 assert_response :success
39 assert_response :success
40 assert_equal 'application/xml', @response.content_type
40 assert_equal 'application/xml', @response.content_type
41 assert_select 'time_entries[type=array] time_entry', 2
41 assert_select 'time_entries[type=array] time_entry', 2
42 end
42 end
43
43
44 test "GET /time_entries/:id.xml should return the time entry" do
44 test "GET /time_entries/:id.xml should return the time entry" do
45 get '/time_entries/2.xml', {}, credentials('jsmith')
45 get '/time_entries/2.xml', {}, credentials('jsmith')
46 assert_response :success
46 assert_response :success
47 assert_equal 'application/xml', @response.content_type
47 assert_equal 'application/xml', @response.content_type
48 assert_select 'time_entry id', :text => '2'
48 assert_select 'time_entry id', :text => '2'
49 end
49 end
50
50
51 test "GET /time_entries/:id.xml on closed project should return the time entry" do
52 project = TimeEntry.find(2).project
53 project.close
54 project.save!
55
56 get '/time_entries/2.xml', {}, credentials('jsmith')
57 assert_response :success
58 assert_equal 'application/xml', @response.content_type
59 assert_select 'time_entry id', :text => '2'
60 end
61
51 test "POST /time_entries.xml with issue_id should create time entry" do
62 test "POST /time_entries.xml with issue_id should create time entry" do
52 assert_difference 'TimeEntry.count' do
63 assert_difference 'TimeEntry.count' do
53 post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith')
64 post '/time_entries.xml', {:time_entry => {:issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith')
54 end
65 end
55 assert_response :created
66 assert_response :created
56 assert_equal 'application/xml', @response.content_type
67 assert_equal 'application/xml', @response.content_type
57
68
58 entry = TimeEntry.order('id DESC').first
69 entry = TimeEntry.order('id DESC').first
59 assert_equal 'jsmith', entry.user.login
70 assert_equal 'jsmith', entry.user.login
60 assert_equal Issue.find(1), entry.issue
71 assert_equal Issue.find(1), entry.issue
61 assert_equal Project.find(1), entry.project
72 assert_equal Project.find(1), entry.project
62 assert_equal Date.parse('2010-12-02'), entry.spent_on
73 assert_equal Date.parse('2010-12-02'), entry.spent_on
63 assert_equal 3.5, entry.hours
74 assert_equal 3.5, entry.hours
64 assert_equal TimeEntryActivity.find(11), entry.activity
75 assert_equal TimeEntryActivity.find(11), entry.activity
65 end
76 end
66
77
67 test "POST /time_entries.xml with issue_id should accept custom fields" do
78 test "POST /time_entries.xml with issue_id should accept custom fields" do
68 field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string')
79 field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'string')
69
80
70 assert_difference 'TimeEntry.count' do
81 assert_difference 'TimeEntry.count' do
71 post '/time_entries.xml', {:time_entry => {
82 post '/time_entries.xml', {:time_entry => {
72 :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}]
83 :issue_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11', :custom_fields => [{:id => field.id.to_s, :value => 'accepted'}]
73 }}, credentials('jsmith')
84 }}, credentials('jsmith')
74 end
85 end
75 assert_response :created
86 assert_response :created
76 assert_equal 'application/xml', @response.content_type
87 assert_equal 'application/xml', @response.content_type
77
88
78 entry = TimeEntry.order('id DESC').first
89 entry = TimeEntry.order('id DESC').first
79 assert_equal 'accepted', entry.custom_field_value(field)
90 assert_equal 'accepted', entry.custom_field_value(field)
80 end
91 end
81
92
82 test "POST /time_entries.xml with project_id should create time entry" do
93 test "POST /time_entries.xml with project_id should create time entry" do
83 assert_difference 'TimeEntry.count' do
94 assert_difference 'TimeEntry.count' do
84 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith')
95 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :hours => '3.5', :activity_id => '11'}}, credentials('jsmith')
85 end
96 end
86 assert_response :created
97 assert_response :created
87 assert_equal 'application/xml', @response.content_type
98 assert_equal 'application/xml', @response.content_type
88
99
89 entry = TimeEntry.order('id DESC').first
100 entry = TimeEntry.order('id DESC').first
90 assert_equal 'jsmith', entry.user.login
101 assert_equal 'jsmith', entry.user.login
91 assert_nil entry.issue
102 assert_nil entry.issue
92 assert_equal Project.find(1), entry.project
103 assert_equal Project.find(1), entry.project
93 assert_equal Date.parse('2010-12-02'), entry.spent_on
104 assert_equal Date.parse('2010-12-02'), entry.spent_on
94 assert_equal 3.5, entry.hours
105 assert_equal 3.5, entry.hours
95 assert_equal TimeEntryActivity.find(11), entry.activity
106 assert_equal TimeEntryActivity.find(11), entry.activity
96 end
107 end
97
108
98 test "POST /time_entries.xml with invalid parameters should return errors" do
109 test "POST /time_entries.xml with invalid parameters should return errors" do
99 assert_no_difference 'TimeEntry.count' do
110 assert_no_difference 'TimeEntry.count' do
100 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith')
111 post '/time_entries.xml', {:time_entry => {:project_id => '1', :spent_on => '2010-12-02', :activity_id => '11'}}, credentials('jsmith')
101 end
112 end
102 assert_response :unprocessable_entity
113 assert_response :unprocessable_entity
103 assert_equal 'application/xml', @response.content_type
114 assert_equal 'application/xml', @response.content_type
104
115
105 assert_select 'errors error', :text => "Hours cannot be blank"
116 assert_select 'errors error', :text => "Hours cannot be blank"
106 end
117 end
107
118
108 test "PUT /time_entries/:id.xml with valid parameters should update time entry" do
119 test "PUT /time_entries/:id.xml with valid parameters should update time entry" do
109 assert_no_difference 'TimeEntry.count' do
120 assert_no_difference 'TimeEntry.count' do
110 put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith')
121 put '/time_entries/2.xml', {:time_entry => {:comments => 'API Update'}}, credentials('jsmith')
111 end
122 end
112 assert_response :ok
123 assert_response :ok
113 assert_equal '', @response.body
124 assert_equal '', @response.body
114 assert_equal 'API Update', TimeEntry.find(2).comments
125 assert_equal 'API Update', TimeEntry.find(2).comments
115 end
126 end
116
127
117 test "PUT /time_entries/:id.xml with invalid parameters should return errors" do
128 test "PUT /time_entries/:id.xml with invalid parameters should return errors" do
118 assert_no_difference 'TimeEntry.count' do
129 assert_no_difference 'TimeEntry.count' do
119 put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith')
130 put '/time_entries/2.xml', {:time_entry => {:hours => '', :comments => 'API Update'}}, credentials('jsmith')
120 end
131 end
121 assert_response :unprocessable_entity
132 assert_response :unprocessable_entity
122 assert_equal 'application/xml', @response.content_type
133 assert_equal 'application/xml', @response.content_type
123
134
124 assert_select 'errors error', :text => "Hours cannot be blank"
135 assert_select 'errors error', :text => "Hours cannot be blank"
125 end
136 end
126
137
127 test "DELETE /time_entries/:id.xml should destroy time entry" do
138 test "DELETE /time_entries/:id.xml should destroy time entry" do
128 assert_difference 'TimeEntry.count', -1 do
139 assert_difference 'TimeEntry.count', -1 do
129 delete '/time_entries/2.xml', {}, credentials('jsmith')
140 delete '/time_entries/2.xml', {}, credentials('jsmith')
130 end
141 end
131 assert_response :ok
142 assert_response :ok
132 assert_equal '', @response.body
143 assert_equal '', @response.body
133 assert_nil TimeEntry.find_by_id(2)
144 assert_nil TimeEntry.find_by_id(2)
134 end
145 end
135 end
146 end
General Comments 0
You need to be logged in to leave comments. Login now