##// END OF EJS Templates
Merged r11760 from trunk (#13850)....
Jean-Philippe Lang -
r11607:af632568e3e9
parent child
Show More
@@ -1,289 +1,290
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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 Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 after_update :update_issues_from_sharing_change
20 after_update :update_issues_from_sharing_change
21 belongs_to :project
21 belongs_to :project
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
23 acts_as_customizable
23 acts_as_customizable
24 acts_as_attachable :view_permission => :view_files,
24 acts_as_attachable :view_permission => :view_files,
25 :delete_permission => :manage_files
25 :delete_permission => :manage_files
26
26
27 VERSION_STATUSES = %w(open locked closed)
27 VERSION_STATUSES = %w(open locked closed)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
29
29
30 validates_presence_of :name
30 validates_presence_of :name
31 validates_uniqueness_of :name, :scope => [:project_id]
31 validates_uniqueness_of :name, :scope => [:project_id]
32 validates_length_of :name, :maximum => 60
32 validates_length_of :name, :maximum => 60
33 validates :effective_date, :date => true
33 validates :effective_date, :date => true
34 validates_inclusion_of :status, :in => VERSION_STATUSES
34 validates_inclusion_of :status, :in => VERSION_STATUSES
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
36
36
37 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
37 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
38 scope :open, lambda { where(:status => 'open') }
38 scope :open, lambda { where(:status => 'open') }
39 scope :visible, lambda {|*args|
39 scope :visible, lambda {|*args|
40 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
40 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
41 }
41 }
42
42
43 safe_attributes 'name',
43 safe_attributes 'name',
44 'description',
44 'description',
45 'effective_date',
45 'effective_date',
46 'due_date',
46 'due_date',
47 'wiki_page_title',
47 'wiki_page_title',
48 'status',
48 'status',
49 'sharing',
49 'sharing',
50 'custom_field_values'
50 'custom_field_values',
51 'custom_fields'
51
52
52 # Returns true if +user+ or current user is allowed to view the version
53 # Returns true if +user+ or current user is allowed to view the version
53 def visible?(user=User.current)
54 def visible?(user=User.current)
54 user.allowed_to?(:view_issues, self.project)
55 user.allowed_to?(:view_issues, self.project)
55 end
56 end
56
57
57 # Version files have same visibility as project files
58 # Version files have same visibility as project files
58 def attachments_visible?(*args)
59 def attachments_visible?(*args)
59 project.present? && project.attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
60 end
61 end
61
62
62 def start_date
63 def start_date
63 @start_date ||= fixed_issues.minimum('start_date')
64 @start_date ||= fixed_issues.minimum('start_date')
64 end
65 end
65
66
66 def due_date
67 def due_date
67 effective_date
68 effective_date
68 end
69 end
69
70
70 def due_date=(arg)
71 def due_date=(arg)
71 self.effective_date=(arg)
72 self.effective_date=(arg)
72 end
73 end
73
74
74 # Returns the total estimated time for this version
75 # Returns the total estimated time for this version
75 # (sum of leaves estimated_hours)
76 # (sum of leaves estimated_hours)
76 def estimated_hours
77 def estimated_hours
77 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
78 end
79 end
79
80
80 # Returns the total reported time for this version
81 # Returns the total reported time for this version
81 def spent_hours
82 def spent_hours
82 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
83 end
84 end
84
85
85 def closed?
86 def closed?
86 status == 'closed'
87 status == 'closed'
87 end
88 end
88
89
89 def open?
90 def open?
90 status == 'open'
91 status == 'open'
91 end
92 end
92
93
93 # Returns true if the version is completed: due date reached and no open issues
94 # Returns true if the version is completed: due date reached and no open issues
94 def completed?
95 def completed?
95 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
96 end
97 end
97
98
98 def behind_schedule?
99 def behind_schedule?
99 if completed_percent == 100
100 if completed_percent == 100
100 return false
101 return false
101 elsif due_date && start_date
102 elsif due_date && start_date
102 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
103 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
103 return done_date <= Date.today
104 return done_date <= Date.today
104 else
105 else
105 false # No issues so it's not late
106 false # No issues so it's not late
106 end
107 end
107 end
108 end
108
109
109 # Returns the completion percentage of this version based on the amount of open/closed issues
110 # Returns the completion percentage of this version based on the amount of open/closed issues
110 # and the time spent on the open issues.
111 # and the time spent on the open issues.
111 def completed_percent
112 def completed_percent
112 if issues_count == 0
113 if issues_count == 0
113 0
114 0
114 elsif open_issues_count == 0
115 elsif open_issues_count == 0
115 100
116 100
116 else
117 else
117 issues_progress(false) + issues_progress(true)
118 issues_progress(false) + issues_progress(true)
118 end
119 end
119 end
120 end
120
121
121 # TODO: remove in Redmine 3.0
122 # TODO: remove in Redmine 3.0
122 def completed_pourcent
123 def completed_pourcent
123 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
124 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
124 completed_percent
125 completed_percent
125 end
126 end
126
127
127 # Returns the percentage of issues that have been marked as 'closed'.
128 # Returns the percentage of issues that have been marked as 'closed'.
128 def closed_percent
129 def closed_percent
129 if issues_count == 0
130 if issues_count == 0
130 0
131 0
131 else
132 else
132 issues_progress(false)
133 issues_progress(false)
133 end
134 end
134 end
135 end
135
136
136 # TODO: remove in Redmine 3.0
137 # TODO: remove in Redmine 3.0
137 def closed_pourcent
138 def closed_pourcent
138 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
139 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
139 closed_percent
140 closed_percent
140 end
141 end
141
142
142 # Returns true if the version is overdue: due date reached and some open issues
143 # Returns true if the version is overdue: due date reached and some open issues
143 def overdue?
144 def overdue?
144 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
145 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
145 end
146 end
146
147
147 # Returns assigned issues count
148 # Returns assigned issues count
148 def issues_count
149 def issues_count
149 load_issue_counts
150 load_issue_counts
150 @issue_count
151 @issue_count
151 end
152 end
152
153
153 # Returns the total amount of open issues for this version.
154 # Returns the total amount of open issues for this version.
154 def open_issues_count
155 def open_issues_count
155 load_issue_counts
156 load_issue_counts
156 @open_issues_count
157 @open_issues_count
157 end
158 end
158
159
159 # Returns the total amount of closed issues for this version.
160 # Returns the total amount of closed issues for this version.
160 def closed_issues_count
161 def closed_issues_count
161 load_issue_counts
162 load_issue_counts
162 @closed_issues_count
163 @closed_issues_count
163 end
164 end
164
165
165 def wiki_page
166 def wiki_page
166 if project.wiki && !wiki_page_title.blank?
167 if project.wiki && !wiki_page_title.blank?
167 @wiki_page ||= project.wiki.find_page(wiki_page_title)
168 @wiki_page ||= project.wiki.find_page(wiki_page_title)
168 end
169 end
169 @wiki_page
170 @wiki_page
170 end
171 end
171
172
172 def to_s; name end
173 def to_s; name end
173
174
174 def to_s_with_project
175 def to_s_with_project
175 "#{project} - #{name}"
176 "#{project} - #{name}"
176 end
177 end
177
178
178 # Versions are sorted by effective_date and name
179 # Versions are sorted by effective_date and name
179 # Those with no effective_date are at the end, sorted by name
180 # Those with no effective_date are at the end, sorted by name
180 def <=>(version)
181 def <=>(version)
181 if self.effective_date
182 if self.effective_date
182 if version.effective_date
183 if version.effective_date
183 if self.effective_date == version.effective_date
184 if self.effective_date == version.effective_date
184 name == version.name ? id <=> version.id : name <=> version.name
185 name == version.name ? id <=> version.id : name <=> version.name
185 else
186 else
186 self.effective_date <=> version.effective_date
187 self.effective_date <=> version.effective_date
187 end
188 end
188 else
189 else
189 -1
190 -1
190 end
191 end
191 else
192 else
192 if version.effective_date
193 if version.effective_date
193 1
194 1
194 else
195 else
195 name == version.name ? id <=> version.id : name <=> version.name
196 name == version.name ? id <=> version.id : name <=> version.name
196 end
197 end
197 end
198 end
198 end
199 end
199
200
200 def self.fields_for_order_statement(table=nil)
201 def self.fields_for_order_statement(table=nil)
201 table ||= table_name
202 table ||= table_name
202 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
203 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
203 end
204 end
204
205
205 scope :sorted, order(fields_for_order_statement)
206 scope :sorted, order(fields_for_order_statement)
206
207
207 # Returns the sharings that +user+ can set the version to
208 # Returns the sharings that +user+ can set the version to
208 def allowed_sharings(user = User.current)
209 def allowed_sharings(user = User.current)
209 VERSION_SHARINGS.select do |s|
210 VERSION_SHARINGS.select do |s|
210 if sharing == s
211 if sharing == s
211 true
212 true
212 else
213 else
213 case s
214 case s
214 when 'system'
215 when 'system'
215 # Only admin users can set a systemwide sharing
216 # Only admin users can set a systemwide sharing
216 user.admin?
217 user.admin?
217 when 'hierarchy', 'tree'
218 when 'hierarchy', 'tree'
218 # Only users allowed to manage versions of the root project can
219 # Only users allowed to manage versions of the root project can
219 # set sharing to hierarchy or tree
220 # set sharing to hierarchy or tree
220 project.nil? || user.allowed_to?(:manage_versions, project.root)
221 project.nil? || user.allowed_to?(:manage_versions, project.root)
221 else
222 else
222 true
223 true
223 end
224 end
224 end
225 end
225 end
226 end
226 end
227 end
227
228
228 private
229 private
229
230
230 def load_issue_counts
231 def load_issue_counts
231 unless @issue_count
232 unless @issue_count
232 @open_issues_count = 0
233 @open_issues_count = 0
233 @closed_issues_count = 0
234 @closed_issues_count = 0
234 fixed_issues.count(:all, :group => :status).each do |status, count|
235 fixed_issues.count(:all, :group => :status).each do |status, count|
235 if status.is_closed?
236 if status.is_closed?
236 @closed_issues_count += count
237 @closed_issues_count += count
237 else
238 else
238 @open_issues_count += count
239 @open_issues_count += count
239 end
240 end
240 end
241 end
241 @issue_count = @open_issues_count + @closed_issues_count
242 @issue_count = @open_issues_count + @closed_issues_count
242 end
243 end
243 end
244 end
244
245
245 # Update the issue's fixed versions. Used if a version's sharing changes.
246 # Update the issue's fixed versions. Used if a version's sharing changes.
246 def update_issues_from_sharing_change
247 def update_issues_from_sharing_change
247 if sharing_changed?
248 if sharing_changed?
248 if VERSION_SHARINGS.index(sharing_was).nil? ||
249 if VERSION_SHARINGS.index(sharing_was).nil? ||
249 VERSION_SHARINGS.index(sharing).nil? ||
250 VERSION_SHARINGS.index(sharing).nil? ||
250 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
251 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
251 Issue.update_versions_from_sharing_change self
252 Issue.update_versions_from_sharing_change self
252 end
253 end
253 end
254 end
254 end
255 end
255
256
256 # Returns the average estimated time of assigned issues
257 # Returns the average estimated time of assigned issues
257 # or 1 if no issue has an estimated time
258 # or 1 if no issue has an estimated time
258 # Used to weigth unestimated issues in progress calculation
259 # Used to weigth unestimated issues in progress calculation
259 def estimated_average
260 def estimated_average
260 if @estimated_average.nil?
261 if @estimated_average.nil?
261 average = fixed_issues.average(:estimated_hours).to_f
262 average = fixed_issues.average(:estimated_hours).to_f
262 if average == 0
263 if average == 0
263 average = 1
264 average = 1
264 end
265 end
265 @estimated_average = average
266 @estimated_average = average
266 end
267 end
267 @estimated_average
268 @estimated_average
268 end
269 end
269
270
270 # Returns the total progress of open or closed issues. The returned percentage takes into account
271 # Returns the total progress of open or closed issues. The returned percentage takes into account
271 # the amount of estimated time set for this version.
272 # the amount of estimated time set for this version.
272 #
273 #
273 # Examples:
274 # Examples:
274 # issues_progress(true) => returns the progress percentage for open issues.
275 # issues_progress(true) => returns the progress percentage for open issues.
275 # issues_progress(false) => returns the progress percentage for closed issues.
276 # issues_progress(false) => returns the progress percentage for closed issues.
276 def issues_progress(open)
277 def issues_progress(open)
277 @issues_progress ||= {}
278 @issues_progress ||= {}
278 @issues_progress[open] ||= begin
279 @issues_progress[open] ||= begin
279 progress = 0
280 progress = 0
280 if issues_count > 0
281 if issues_count > 0
281 ratio = open ? 'done_ratio' : 100
282 ratio = open ? 'done_ratio' : 100
282
283
283 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
284 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
284 progress = done / (estimated_average * issues_count)
285 progress = done / (estimated_average * issues_count)
285 end
286 end
286 progress
287 progress
287 end
288 end
288 end
289 end
289 end
290 end
@@ -1,135 +1,158
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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::VersionsTest < Redmine::ApiTest::Base
20 class Redmine::ApiTest::VersionsTest < 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 :versions
28 :versions
29
29
30 def setup
30 def setup
31 Setting.rest_api_enabled = '1'
31 Setting.rest_api_enabled = '1'
32 end
32 end
33
33
34 context "/projects/:project_id/versions" do
34 context "/projects/:project_id/versions" do
35 context "GET" do
35 context "GET" do
36 should "return project versions" do
36 should "return project versions" do
37 get '/projects/1/versions.xml'
37 get '/projects/1/versions.xml'
38
38
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_tag :tag => 'versions',
41 assert_tag :tag => 'versions',
42 :attributes => {:type => 'array'},
42 :attributes => {:type => 'array'},
43 :child => {
43 :child => {
44 :tag => 'version',
44 :tag => 'version',
45 :child => {
45 :child => {
46 :tag => 'id',
46 :tag => 'id',
47 :content => '2',
47 :content => '2',
48 :sibling => {
48 :sibling => {
49 :tag => 'name',
49 :tag => 'name',
50 :content => '1.0'
50 :content => '1.0'
51 }
51 }
52 }
52 }
53 }
53 }
54 end
54 end
55 end
55 end
56
56
57 context "POST" do
57 context "POST" do
58 should "create the version" do
58 should "create the version" do
59 assert_difference 'Version.count' do
59 assert_difference 'Version.count' do
60 post '/projects/1/versions.xml', {:version => {:name => 'API test'}}, credentials('jsmith')
60 post '/projects/1/versions.xml', {:version => {:name => 'API test'}}, credentials('jsmith')
61 end
61 end
62
62
63 version = Version.first(:order => 'id DESC')
63 version = Version.first(:order => 'id DESC')
64 assert_equal 'API test', version.name
64 assert_equal 'API test', version.name
65
65
66 assert_response :created
66 assert_response :created
67 assert_equal 'application/xml', @response.content_type
67 assert_equal 'application/xml', @response.content_type
68 assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s}
68 assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s}
69 end
69 end
70
70
71 should "create the version with due date" do
71 should "create the version with due date" do
72 assert_difference 'Version.count' do
72 assert_difference 'Version.count' do
73 post '/projects/1/versions.xml', {:version => {:name => 'API test', :due_date => '2012-01-24'}}, credentials('jsmith')
73 post '/projects/1/versions.xml', {:version => {:name => 'API test', :due_date => '2012-01-24'}}, credentials('jsmith')
74 end
74 end
75
75
76 version = Version.first(:order => 'id DESC')
76 version = Version.first(:order => 'id DESC')
77 assert_equal 'API test', version.name
77 assert_equal 'API test', version.name
78 assert_equal Date.parse('2012-01-24'), version.due_date
78 assert_equal Date.parse('2012-01-24'), version.due_date
79
79
80 assert_response :created
80 assert_response :created
81 assert_equal 'application/xml', @response.content_type
81 assert_equal 'application/xml', @response.content_type
82 assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s}
82 assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s}
83 end
83 end
84
84
85 should "create the version with custom fields" do
86 field = VersionCustomField.generate!
87
88 assert_difference 'Version.count' do
89 post '/projects/1/versions.xml', {
90 :version => {
91 :name => 'API test',
92 :custom_fields => [
93 {'id' => field.id.to_s, 'value' => 'Some value'}
94 ]
95 }
96 }, credentials('jsmith')
97 end
98
99 version = Version.first(:order => 'id DESC')
100 assert_equal 'API test', version.name
101 assert_equal 'Some value', version.custom_field_value(field)
102
103 assert_response :created
104 assert_equal 'application/xml', @response.content_type
105 assert_select 'version>custom_fields>custom_field[id=?]>value', field.id.to_s, 'Some value'
106 end
107
85 context "with failure" do
108 context "with failure" do
86 should "return the errors" do
109 should "return the errors" do
87 assert_no_difference('Version.count') do
110 assert_no_difference('Version.count') do
88 post '/projects/1/versions.xml', {:version => {:name => ''}}, credentials('jsmith')
111 post '/projects/1/versions.xml', {:version => {:name => ''}}, credentials('jsmith')
89 end
112 end
90
113
91 assert_response :unprocessable_entity
114 assert_response :unprocessable_entity
92 assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
115 assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
93 end
116 end
94 end
117 end
95 end
118 end
96 end
119 end
97
120
98 context "/versions/:id" do
121 context "/versions/:id" do
99 context "GET" do
122 context "GET" do
100 should "return the version" do
123 should "return the version" do
101 get '/versions/2.xml'
124 get '/versions/2.xml'
102
125
103 assert_response :success
126 assert_response :success
104 assert_equal 'application/xml', @response.content_type
127 assert_equal 'application/xml', @response.content_type
105 assert_select 'version' do
128 assert_select 'version' do
106 assert_select 'id', :text => '2'
129 assert_select 'id', :text => '2'
107 assert_select 'name', :text => '1.0'
130 assert_select 'name', :text => '1.0'
108 assert_select 'sharing', :text => 'none'
131 assert_select 'sharing', :text => 'none'
109 end
132 end
110 end
133 end
111 end
134 end
112
135
113 context "PUT" do
136 context "PUT" do
114 should "update the version" do
137 should "update the version" do
115 put '/versions/2.xml', {:version => {:name => 'API update'}}, credentials('jsmith')
138 put '/versions/2.xml', {:version => {:name => 'API update'}}, credentials('jsmith')
116
139
117 assert_response :ok
140 assert_response :ok
118 assert_equal '', @response.body
141 assert_equal '', @response.body
119 assert_equal 'API update', Version.find(2).name
142 assert_equal 'API update', Version.find(2).name
120 end
143 end
121 end
144 end
122
145
123 context "DELETE" do
146 context "DELETE" do
124 should "destroy the version" do
147 should "destroy the version" do
125 assert_difference 'Version.count', -1 do
148 assert_difference 'Version.count', -1 do
126 delete '/versions/3.xml', {}, credentials('jsmith')
149 delete '/versions/3.xml', {}, credentials('jsmith')
127 end
150 end
128
151
129 assert_response :ok
152 assert_response :ok
130 assert_equal '', @response.body
153 assert_equal '', @response.body
131 assert_nil Version.find_by_id(3)
154 assert_nil Version.find_by_id(3)
132 end
155 end
133 end
156 end
134 end
157 end
135 end
158 end
@@ -1,151 +1,162
1 module ObjectHelpers
1 module ObjectHelpers
2 def User.generate!(attributes={})
2 def User.generate!(attributes={})
3 @generated_user_login ||= 'user0'
3 @generated_user_login ||= 'user0'
4 @generated_user_login.succ!
4 @generated_user_login.succ!
5 user = User.new(attributes)
5 user = User.new(attributes)
6 user.login = @generated_user_login.dup if user.login.blank?
6 user.login = @generated_user_login.dup if user.login.blank?
7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
8 user.firstname = "Bob" if user.firstname.blank?
8 user.firstname = "Bob" if user.firstname.blank?
9 user.lastname = "Doe" if user.lastname.blank?
9 user.lastname = "Doe" if user.lastname.blank?
10 yield user if block_given?
10 yield user if block_given?
11 user.save!
11 user.save!
12 user
12 user
13 end
13 end
14
14
15 def User.add_to_project(user, project, roles=nil)
15 def User.add_to_project(user, project, roles=nil)
16 roles = Role.find(1) if roles.nil?
16 roles = Role.find(1) if roles.nil?
17 roles = [roles] unless roles.is_a?(Array)
17 roles = [roles] unless roles.is_a?(Array)
18 Member.create!(:principal => user, :project => project, :roles => roles)
18 Member.create!(:principal => user, :project => project, :roles => roles)
19 end
19 end
20
20
21 def Group.generate!(attributes={})
21 def Group.generate!(attributes={})
22 @generated_group_name ||= 'Group 0'
22 @generated_group_name ||= 'Group 0'
23 @generated_group_name.succ!
23 @generated_group_name.succ!
24 group = Group.new(attributes)
24 group = Group.new(attributes)
25 group.name = @generated_group_name.dup if group.name.blank?
25 group.name = @generated_group_name.dup if group.name.blank?
26 yield group if block_given?
26 yield group if block_given?
27 group.save!
27 group.save!
28 group
28 group
29 end
29 end
30
30
31 def Project.generate!(attributes={})
31 def Project.generate!(attributes={})
32 @generated_project_identifier ||= 'project-0000'
32 @generated_project_identifier ||= 'project-0000'
33 @generated_project_identifier.succ!
33 @generated_project_identifier.succ!
34 project = Project.new(attributes)
34 project = Project.new(attributes)
35 project.name = @generated_project_identifier.dup if project.name.blank?
35 project.name = @generated_project_identifier.dup if project.name.blank?
36 project.identifier = @generated_project_identifier.dup if project.identifier.blank?
36 project.identifier = @generated_project_identifier.dup if project.identifier.blank?
37 yield project if block_given?
37 yield project if block_given?
38 project.save!
38 project.save!
39 project
39 project
40 end
40 end
41
41
42 def Project.generate_with_parent!(parent, attributes={})
42 def Project.generate_with_parent!(parent, attributes={})
43 project = Project.generate!(attributes)
43 project = Project.generate!(attributes)
44 project.set_parent!(parent)
44 project.set_parent!(parent)
45 project
45 project
46 end
46 end
47
47
48 def Tracker.generate!(attributes={})
48 def Tracker.generate!(attributes={})
49 @generated_tracker_name ||= 'Tracker 0'
49 @generated_tracker_name ||= 'Tracker 0'
50 @generated_tracker_name.succ!
50 @generated_tracker_name.succ!
51 tracker = Tracker.new(attributes)
51 tracker = Tracker.new(attributes)
52 tracker.name = @generated_tracker_name.dup if tracker.name.blank?
52 tracker.name = @generated_tracker_name.dup if tracker.name.blank?
53 yield tracker if block_given?
53 yield tracker if block_given?
54 tracker.save!
54 tracker.save!
55 tracker
55 tracker
56 end
56 end
57
57
58 def Role.generate!(attributes={})
58 def Role.generate!(attributes={})
59 @generated_role_name ||= 'Role 0'
59 @generated_role_name ||= 'Role 0'
60 @generated_role_name.succ!
60 @generated_role_name.succ!
61 role = Role.new(attributes)
61 role = Role.new(attributes)
62 role.name = @generated_role_name.dup if role.name.blank?
62 role.name = @generated_role_name.dup if role.name.blank?
63 yield role if block_given?
63 yield role if block_given?
64 role.save!
64 role.save!
65 role
65 role
66 end
66 end
67
67
68 def Issue.generate!(attributes={})
68 def Issue.generate!(attributes={})
69 issue = Issue.new(attributes)
69 issue = Issue.new(attributes)
70 issue.project ||= Project.find(1)
70 issue.project ||= Project.find(1)
71 issue.tracker ||= issue.project.trackers.first
71 issue.tracker ||= issue.project.trackers.first
72 issue.subject = 'Generated' if issue.subject.blank?
72 issue.subject = 'Generated' if issue.subject.blank?
73 issue.author ||= User.find(2)
73 issue.author ||= User.find(2)
74 yield issue if block_given?
74 yield issue if block_given?
75 issue.save!
75 issue.save!
76 issue
76 issue
77 end
77 end
78
78
79 # Generates an issue with 2 children and a grandchild
79 # Generates an issue with 2 children and a grandchild
80 def Issue.generate_with_descendants!(attributes={})
80 def Issue.generate_with_descendants!(attributes={})
81 issue = Issue.generate!(attributes)
81 issue = Issue.generate!(attributes)
82 child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id)
82 child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id)
83 Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id)
83 Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id)
84 Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id)
84 Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id)
85 issue.reload
85 issue.reload
86 end
86 end
87
87
88 def Journal.generate!(attributes={})
88 def Journal.generate!(attributes={})
89 journal = Journal.new(attributes)
89 journal = Journal.new(attributes)
90 journal.user ||= User.first
90 journal.user ||= User.first
91 journal.journalized ||= Issue.first
91 journal.journalized ||= Issue.first
92 yield journal if block_given?
92 yield journal if block_given?
93 journal.save!
93 journal.save!
94 journal
94 journal
95 end
95 end
96
96
97 def Version.generate!(attributes={})
97 def Version.generate!(attributes={})
98 @generated_version_name ||= 'Version 0'
98 @generated_version_name ||= 'Version 0'
99 @generated_version_name.succ!
99 @generated_version_name.succ!
100 version = Version.new(attributes)
100 version = Version.new(attributes)
101 version.name = @generated_version_name.dup if version.name.blank?
101 version.name = @generated_version_name.dup if version.name.blank?
102 yield version if block_given?
102 yield version if block_given?
103 version.save!
103 version.save!
104 version
104 version
105 end
105 end
106
106
107 def TimeEntry.generate!(attributes={})
107 def TimeEntry.generate!(attributes={})
108 entry = TimeEntry.new(attributes)
108 entry = TimeEntry.new(attributes)
109 entry.user ||= User.find(2)
109 entry.user ||= User.find(2)
110 entry.issue ||= Issue.find(1) unless entry.project
110 entry.issue ||= Issue.find(1) unless entry.project
111 entry.project ||= entry.issue.project
111 entry.project ||= entry.issue.project
112 entry.activity ||= TimeEntryActivity.first
112 entry.activity ||= TimeEntryActivity.first
113 entry.spent_on ||= Date.today
113 entry.spent_on ||= Date.today
114 entry.hours ||= 1.0
114 entry.hours ||= 1.0
115 entry.save!
115 entry.save!
116 entry
116 entry
117 end
117 end
118
118
119 def AuthSource.generate!(attributes={})
119 def AuthSource.generate!(attributes={})
120 @generated_auth_source_name ||= 'Auth 0'
120 @generated_auth_source_name ||= 'Auth 0'
121 @generated_auth_source_name.succ!
121 @generated_auth_source_name.succ!
122 source = AuthSource.new(attributes)
122 source = AuthSource.new(attributes)
123 source.name = @generated_auth_source_name.dup if source.name.blank?
123 source.name = @generated_auth_source_name.dup if source.name.blank?
124 yield source if block_given?
124 yield source if block_given?
125 source.save!
125 source.save!
126 source
126 source
127 end
127 end
128
128
129 def Board.generate!(attributes={})
129 def Board.generate!(attributes={})
130 @generated_board_name ||= 'Forum 0'
130 @generated_board_name ||= 'Forum 0'
131 @generated_board_name.succ!
131 @generated_board_name.succ!
132 board = Board.new(attributes)
132 board = Board.new(attributes)
133 board.name = @generated_board_name.dup if board.name.blank?
133 board.name = @generated_board_name.dup if board.name.blank?
134 board.description = @generated_board_name.dup if board.description.blank?
134 board.description = @generated_board_name.dup if board.description.blank?
135 yield board if block_given?
135 yield board if block_given?
136 board.save!
136 board.save!
137 board
137 board
138 end
138 end
139
139
140 def Attachment.generate!(attributes={})
140 def Attachment.generate!(attributes={})
141 @generated_filename ||= 'testfile0'
141 @generated_filename ||= 'testfile0'
142 @generated_filename.succ!
142 @generated_filename.succ!
143 attributes = attributes.dup
143 attributes = attributes.dup
144 attachment = Attachment.new(attributes)
144 attachment = Attachment.new(attributes)
145 attachment.container ||= Issue.find(1)
145 attachment.container ||= Issue.find(1)
146 attachment.author ||= User.find(2)
146 attachment.author ||= User.find(2)
147 attachment.filename = @generated_filename.dup if attachment.filename.blank?
147 attachment.filename = @generated_filename.dup if attachment.filename.blank?
148 attachment.save!
148 attachment.save!
149 attachment
149 attachment
150 end
150 end
151
152 def CustomField.generate!(attributes={})
153 @generated_custom_field_name ||= 'Custom field 0'
154 @generated_custom_field_name.succ!
155 field = new(attributes)
156 field.name = @generated_custom_field_name.dup if field.name.blank?
157 field.field_format = 'string' if field.field_format.blank?
158 yield field if block_given?
159 field.save!
160 field
161 end
151 end
162 end
General Comments 0
You need to be logged in to leave comments. Login now