##// END OF EJS Templates
Merged r13390 (#17163)....
Jean-Philippe Lang -
r13034:198e92d26b49
parent child
Show More
@@ -1,290 +1,294
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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 'custom_fields'
52
52
53 # 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
54 def visible?(user=User.current)
54 def visible?(user=User.current)
55 user.allowed_to?(:view_issues, self.project)
55 user.allowed_to?(:view_issues, self.project)
56 end
56 end
57
57
58 # Version files have same visibility as project files
58 # Version files have same visibility as project files
59 def attachments_visible?(*args)
59 def attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
61 end
61 end
62
62
63 def attachments_deletable?(usr=User.current)
64 project.present? && project.attachments_deletable?(usr)
65 end
66
63 def start_date
67 def start_date
64 @start_date ||= fixed_issues.minimum('start_date')
68 @start_date ||= fixed_issues.minimum('start_date')
65 end
69 end
66
70
67 def due_date
71 def due_date
68 effective_date
72 effective_date
69 end
73 end
70
74
71 def due_date=(arg)
75 def due_date=(arg)
72 self.effective_date=(arg)
76 self.effective_date=(arg)
73 end
77 end
74
78
75 # Returns the total estimated time for this version
79 # Returns the total estimated time for this version
76 # (sum of leaves estimated_hours)
80 # (sum of leaves estimated_hours)
77 def estimated_hours
81 def estimated_hours
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
82 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
79 end
83 end
80
84
81 # Returns the total reported time for this version
85 # Returns the total reported time for this version
82 def spent_hours
86 def spent_hours
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
87 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
84 end
88 end
85
89
86 def closed?
90 def closed?
87 status == 'closed'
91 status == 'closed'
88 end
92 end
89
93
90 def open?
94 def open?
91 status == 'open'
95 status == 'open'
92 end
96 end
93
97
94 # Returns true if the version is completed: due date reached and no open issues
98 # Returns true if the version is completed: due date reached and no open issues
95 def completed?
99 def completed?
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
100 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
97 end
101 end
98
102
99 def behind_schedule?
103 def behind_schedule?
100 if completed_percent == 100
104 if completed_percent == 100
101 return false
105 return false
102 elsif due_date && start_date
106 elsif due_date && start_date
103 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
107 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
104 return done_date <= Date.today
108 return done_date <= Date.today
105 else
109 else
106 false # No issues so it's not late
110 false # No issues so it's not late
107 end
111 end
108 end
112 end
109
113
110 # Returns the completion percentage of this version based on the amount of open/closed issues
114 # Returns the completion percentage of this version based on the amount of open/closed issues
111 # and the time spent on the open issues.
115 # and the time spent on the open issues.
112 def completed_percent
116 def completed_percent
113 if issues_count == 0
117 if issues_count == 0
114 0
118 0
115 elsif open_issues_count == 0
119 elsif open_issues_count == 0
116 100
120 100
117 else
121 else
118 issues_progress(false) + issues_progress(true)
122 issues_progress(false) + issues_progress(true)
119 end
123 end
120 end
124 end
121
125
122 # TODO: remove in Redmine 3.0
126 # TODO: remove in Redmine 3.0
123 def completed_pourcent
127 def completed_pourcent
124 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
128 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
125 completed_percent
129 completed_percent
126 end
130 end
127
131
128 # Returns the percentage of issues that have been marked as 'closed'.
132 # Returns the percentage of issues that have been marked as 'closed'.
129 def closed_percent
133 def closed_percent
130 if issues_count == 0
134 if issues_count == 0
131 0
135 0
132 else
136 else
133 issues_progress(false)
137 issues_progress(false)
134 end
138 end
135 end
139 end
136
140
137 # TODO: remove in Redmine 3.0
141 # TODO: remove in Redmine 3.0
138 def closed_pourcent
142 def closed_pourcent
139 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
143 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
140 closed_percent
144 closed_percent
141 end
145 end
142
146
143 # Returns true if the version is overdue: due date reached and some open issues
147 # Returns true if the version is overdue: due date reached and some open issues
144 def overdue?
148 def overdue?
145 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
149 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
146 end
150 end
147
151
148 # Returns assigned issues count
152 # Returns assigned issues count
149 def issues_count
153 def issues_count
150 load_issue_counts
154 load_issue_counts
151 @issue_count
155 @issue_count
152 end
156 end
153
157
154 # Returns the total amount of open issues for this version.
158 # Returns the total amount of open issues for this version.
155 def open_issues_count
159 def open_issues_count
156 load_issue_counts
160 load_issue_counts
157 @open_issues_count
161 @open_issues_count
158 end
162 end
159
163
160 # Returns the total amount of closed issues for this version.
164 # Returns the total amount of closed issues for this version.
161 def closed_issues_count
165 def closed_issues_count
162 load_issue_counts
166 load_issue_counts
163 @closed_issues_count
167 @closed_issues_count
164 end
168 end
165
169
166 def wiki_page
170 def wiki_page
167 if project.wiki && !wiki_page_title.blank?
171 if project.wiki && !wiki_page_title.blank?
168 @wiki_page ||= project.wiki.find_page(wiki_page_title)
172 @wiki_page ||= project.wiki.find_page(wiki_page_title)
169 end
173 end
170 @wiki_page
174 @wiki_page
171 end
175 end
172
176
173 def to_s; name end
177 def to_s; name end
174
178
175 def to_s_with_project
179 def to_s_with_project
176 "#{project} - #{name}"
180 "#{project} - #{name}"
177 end
181 end
178
182
179 # Versions are sorted by effective_date and name
183 # Versions are sorted by effective_date and name
180 # Those with no effective_date are at the end, sorted by name
184 # Those with no effective_date are at the end, sorted by name
181 def <=>(version)
185 def <=>(version)
182 if self.effective_date
186 if self.effective_date
183 if version.effective_date
187 if version.effective_date
184 if self.effective_date == version.effective_date
188 if self.effective_date == version.effective_date
185 name == version.name ? id <=> version.id : name <=> version.name
189 name == version.name ? id <=> version.id : name <=> version.name
186 else
190 else
187 self.effective_date <=> version.effective_date
191 self.effective_date <=> version.effective_date
188 end
192 end
189 else
193 else
190 -1
194 -1
191 end
195 end
192 else
196 else
193 if version.effective_date
197 if version.effective_date
194 1
198 1
195 else
199 else
196 name == version.name ? id <=> version.id : name <=> version.name
200 name == version.name ? id <=> version.id : name <=> version.name
197 end
201 end
198 end
202 end
199 end
203 end
200
204
201 def self.fields_for_order_statement(table=nil)
205 def self.fields_for_order_statement(table=nil)
202 table ||= table_name
206 table ||= table_name
203 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
207 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
204 end
208 end
205
209
206 scope :sorted, lambda { order(fields_for_order_statement) }
210 scope :sorted, lambda { order(fields_for_order_statement) }
207
211
208 # Returns the sharings that +user+ can set the version to
212 # Returns the sharings that +user+ can set the version to
209 def allowed_sharings(user = User.current)
213 def allowed_sharings(user = User.current)
210 VERSION_SHARINGS.select do |s|
214 VERSION_SHARINGS.select do |s|
211 if sharing == s
215 if sharing == s
212 true
216 true
213 else
217 else
214 case s
218 case s
215 when 'system'
219 when 'system'
216 # Only admin users can set a systemwide sharing
220 # Only admin users can set a systemwide sharing
217 user.admin?
221 user.admin?
218 when 'hierarchy', 'tree'
222 when 'hierarchy', 'tree'
219 # Only users allowed to manage versions of the root project can
223 # Only users allowed to manage versions of the root project can
220 # set sharing to hierarchy or tree
224 # set sharing to hierarchy or tree
221 project.nil? || user.allowed_to?(:manage_versions, project.root)
225 project.nil? || user.allowed_to?(:manage_versions, project.root)
222 else
226 else
223 true
227 true
224 end
228 end
225 end
229 end
226 end
230 end
227 end
231 end
228
232
229 private
233 private
230
234
231 def load_issue_counts
235 def load_issue_counts
232 unless @issue_count
236 unless @issue_count
233 @open_issues_count = 0
237 @open_issues_count = 0
234 @closed_issues_count = 0
238 @closed_issues_count = 0
235 fixed_issues.group(:status).count.each do |status, count|
239 fixed_issues.group(:status).count.each do |status, count|
236 if status.is_closed?
240 if status.is_closed?
237 @closed_issues_count += count
241 @closed_issues_count += count
238 else
242 else
239 @open_issues_count += count
243 @open_issues_count += count
240 end
244 end
241 end
245 end
242 @issue_count = @open_issues_count + @closed_issues_count
246 @issue_count = @open_issues_count + @closed_issues_count
243 end
247 end
244 end
248 end
245
249
246 # Update the issue's fixed versions. Used if a version's sharing changes.
250 # Update the issue's fixed versions. Used if a version's sharing changes.
247 def update_issues_from_sharing_change
251 def update_issues_from_sharing_change
248 if sharing_changed?
252 if sharing_changed?
249 if VERSION_SHARINGS.index(sharing_was).nil? ||
253 if VERSION_SHARINGS.index(sharing_was).nil? ||
250 VERSION_SHARINGS.index(sharing).nil? ||
254 VERSION_SHARINGS.index(sharing).nil? ||
251 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
255 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
252 Issue.update_versions_from_sharing_change self
256 Issue.update_versions_from_sharing_change self
253 end
257 end
254 end
258 end
255 end
259 end
256
260
257 # Returns the average estimated time of assigned issues
261 # Returns the average estimated time of assigned issues
258 # or 1 if no issue has an estimated time
262 # or 1 if no issue has an estimated time
259 # Used to weigth unestimated issues in progress calculation
263 # Used to weigth unestimated issues in progress calculation
260 def estimated_average
264 def estimated_average
261 if @estimated_average.nil?
265 if @estimated_average.nil?
262 average = fixed_issues.average(:estimated_hours).to_f
266 average = fixed_issues.average(:estimated_hours).to_f
263 if average == 0
267 if average == 0
264 average = 1
268 average = 1
265 end
269 end
266 @estimated_average = average
270 @estimated_average = average
267 end
271 end
268 @estimated_average
272 @estimated_average
269 end
273 end
270
274
271 # Returns the total progress of open or closed issues. The returned percentage takes into account
275 # Returns the total progress of open or closed issues. The returned percentage takes into account
272 # the amount of estimated time set for this version.
276 # the amount of estimated time set for this version.
273 #
277 #
274 # Examples:
278 # Examples:
275 # issues_progress(true) => returns the progress percentage for open issues.
279 # issues_progress(true) => returns the progress percentage for open issues.
276 # issues_progress(false) => returns the progress percentage for closed issues.
280 # issues_progress(false) => returns the progress percentage for closed issues.
277 def issues_progress(open)
281 def issues_progress(open)
278 @issues_progress ||= {}
282 @issues_progress ||= {}
279 @issues_progress[open] ||= begin
283 @issues_progress[open] ||= begin
280 progress = 0
284 progress = 0
281 if issues_count > 0
285 if issues_count > 0
282 ratio = open ? 'done_ratio' : 100
286 ratio = open ? 'done_ratio' : 100
283
287
284 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
288 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
285 progress = done / (estimated_average * issues_count)
289 progress = done / (estimated_average * issues_count)
286 end
290 end
287 progress
291 progress
288 end
292 end
289 end
293 end
290 end
294 end
@@ -1,385 +1,395
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class AttachmentsControllerTest < ActionController::TestCase
22 class AttachmentsControllerTest < ActionController::TestCase
23 fixtures :users, :projects, :roles, :members, :member_roles,
23 fixtures :users, :projects, :roles, :members, :member_roles,
24 :enabled_modules, :issues, :trackers, :attachments,
24 :enabled_modules, :issues, :trackers, :attachments,
25 :versions, :wiki_pages, :wikis, :documents
25 :versions, :wiki_pages, :wikis, :documents
26
26
27 def setup
27 def setup
28 User.current = nil
28 User.current = nil
29 set_fixtures_attachments_directory
29 set_fixtures_attachments_directory
30 end
30 end
31
31
32 def teardown
32 def teardown
33 set_tmp_attachments_directory
33 set_tmp_attachments_directory
34 end
34 end
35
35
36 def test_show_diff
36 def test_show_diff
37 ['inline', 'sbs'].each do |dt|
37 ['inline', 'sbs'].each do |dt|
38 # 060719210727_changeset_utf8.diff
38 # 060719210727_changeset_utf8.diff
39 get :show, :id => 14, :type => dt
39 get :show, :id => 14, :type => dt
40 assert_response :success
40 assert_response :success
41 assert_template 'diff'
41 assert_template 'diff'
42 assert_equal 'text/html', @response.content_type
42 assert_equal 'text/html', @response.content_type
43 assert_tag 'th',
43 assert_tag 'th',
44 :attributes => {:class => /filename/},
44 :attributes => {:class => /filename/},
45 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
45 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
46 assert_tag 'td',
46 assert_tag 'td',
47 :attributes => {:class => /line-code/},
47 :attributes => {:class => /line-code/},
48 :content => /Demande créée avec succès/
48 :content => /Demande créée avec succès/
49 end
49 end
50 set_tmp_attachments_directory
50 set_tmp_attachments_directory
51 end
51 end
52
52
53 def test_show_diff_replace_cannot_convert_content
53 def test_show_diff_replace_cannot_convert_content
54 with_settings :repositories_encodings => 'UTF-8' do
54 with_settings :repositories_encodings => 'UTF-8' do
55 ['inline', 'sbs'].each do |dt|
55 ['inline', 'sbs'].each do |dt|
56 # 060719210727_changeset_iso8859-1.diff
56 # 060719210727_changeset_iso8859-1.diff
57 get :show, :id => 5, :type => dt
57 get :show, :id => 5, :type => dt
58 assert_response :success
58 assert_response :success
59 assert_template 'diff'
59 assert_template 'diff'
60 assert_equal 'text/html', @response.content_type
60 assert_equal 'text/html', @response.content_type
61 assert_tag 'th',
61 assert_tag 'th',
62 :attributes => {:class => "filename"},
62 :attributes => {:class => "filename"},
63 :content => /issues_controller.rb\t\(r\?vision 1484\)/
63 :content => /issues_controller.rb\t\(r\?vision 1484\)/
64 assert_tag 'td',
64 assert_tag 'td',
65 :attributes => {:class => /line-code/},
65 :attributes => {:class => /line-code/},
66 :content => /Demande cr\?\?e avec succ\?s/
66 :content => /Demande cr\?\?e avec succ\?s/
67 end
67 end
68 end
68 end
69 set_tmp_attachments_directory
69 set_tmp_attachments_directory
70 end
70 end
71
71
72 def test_show_diff_latin_1
72 def test_show_diff_latin_1
73 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
73 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
74 ['inline', 'sbs'].each do |dt|
74 ['inline', 'sbs'].each do |dt|
75 # 060719210727_changeset_iso8859-1.diff
75 # 060719210727_changeset_iso8859-1.diff
76 get :show, :id => 5, :type => dt
76 get :show, :id => 5, :type => dt
77 assert_response :success
77 assert_response :success
78 assert_template 'diff'
78 assert_template 'diff'
79 assert_equal 'text/html', @response.content_type
79 assert_equal 'text/html', @response.content_type
80 assert_tag 'th',
80 assert_tag 'th',
81 :attributes => {:class => "filename"},
81 :attributes => {:class => "filename"},
82 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
82 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
83 assert_tag 'td',
83 assert_tag 'td',
84 :attributes => {:class => /line-code/},
84 :attributes => {:class => /line-code/},
85 :content => /Demande créée avec succès/
85 :content => /Demande créée avec succès/
86 end
86 end
87 end
87 end
88 set_tmp_attachments_directory
88 set_tmp_attachments_directory
89 end
89 end
90
90
91 def test_save_diff_type
91 def test_save_diff_type
92 user1 = User.find(1)
92 user1 = User.find(1)
93 user1.pref[:diff_type] = nil
93 user1.pref[:diff_type] = nil
94 user1.preference.save
94 user1.preference.save
95 user = User.find(1)
95 user = User.find(1)
96 assert_nil user.pref[:diff_type]
96 assert_nil user.pref[:diff_type]
97
97
98 @request.session[:user_id] = 1 # admin
98 @request.session[:user_id] = 1 # admin
99 get :show, :id => 5
99 get :show, :id => 5
100 assert_response :success
100 assert_response :success
101 assert_template 'diff'
101 assert_template 'diff'
102 user.reload
102 user.reload
103 assert_equal "inline", user.pref[:diff_type]
103 assert_equal "inline", user.pref[:diff_type]
104 get :show, :id => 5, :type => 'sbs'
104 get :show, :id => 5, :type => 'sbs'
105 assert_response :success
105 assert_response :success
106 assert_template 'diff'
106 assert_template 'diff'
107 user.reload
107 user.reload
108 assert_equal "sbs", user.pref[:diff_type]
108 assert_equal "sbs", user.pref[:diff_type]
109 end
109 end
110
110
111 def test_diff_show_filename_in_mercurial_export
111 def test_diff_show_filename_in_mercurial_export
112 set_tmp_attachments_directory
112 set_tmp_attachments_directory
113 a = Attachment.new(:container => Issue.find(1),
113 a = Attachment.new(:container => Issue.find(1),
114 :file => uploaded_test_file("hg-export.diff", "text/plain"),
114 :file => uploaded_test_file("hg-export.diff", "text/plain"),
115 :author => User.find(1))
115 :author => User.find(1))
116 assert a.save
116 assert a.save
117 assert_equal 'hg-export.diff', a.filename
117 assert_equal 'hg-export.diff', a.filename
118
118
119 get :show, :id => a.id, :type => 'inline'
119 get :show, :id => a.id, :type => 'inline'
120 assert_response :success
120 assert_response :success
121 assert_template 'diff'
121 assert_template 'diff'
122 assert_equal 'text/html', @response.content_type
122 assert_equal 'text/html', @response.content_type
123 assert_select 'th.filename', :text => 'test1.txt'
123 assert_select 'th.filename', :text => 'test1.txt'
124 end
124 end
125
125
126 def test_show_text_file
126 def test_show_text_file
127 get :show, :id => 4
127 get :show, :id => 4
128 assert_response :success
128 assert_response :success
129 assert_template 'file'
129 assert_template 'file'
130 assert_equal 'text/html', @response.content_type
130 assert_equal 'text/html', @response.content_type
131 set_tmp_attachments_directory
131 set_tmp_attachments_directory
132 end
132 end
133
133
134 def test_show_text_file_utf_8
134 def test_show_text_file_utf_8
135 set_tmp_attachments_directory
135 set_tmp_attachments_directory
136 a = Attachment.new(:container => Issue.find(1),
136 a = Attachment.new(:container => Issue.find(1),
137 :file => uploaded_test_file("japanese-utf-8.txt", "text/plain"),
137 :file => uploaded_test_file("japanese-utf-8.txt", "text/plain"),
138 :author => User.find(1))
138 :author => User.find(1))
139 assert a.save
139 assert a.save
140 assert_equal 'japanese-utf-8.txt', a.filename
140 assert_equal 'japanese-utf-8.txt', a.filename
141
141
142 str_japanese = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
142 str_japanese = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
143 str_japanese.force_encoding('UTF-8') if str_japanese.respond_to?(:force_encoding)
143 str_japanese.force_encoding('UTF-8') if str_japanese.respond_to?(:force_encoding)
144
144
145 get :show, :id => a.id
145 get :show, :id => a.id
146 assert_response :success
146 assert_response :success
147 assert_template 'file'
147 assert_template 'file'
148 assert_equal 'text/html', @response.content_type
148 assert_equal 'text/html', @response.content_type
149 assert_tag :tag => 'th',
149 assert_tag :tag => 'th',
150 :content => '1',
150 :content => '1',
151 :attributes => { :class => 'line-num' },
151 :attributes => { :class => 'line-num' },
152 :sibling => { :tag => 'td', :content => /#{str_japanese}/ }
152 :sibling => { :tag => 'td', :content => /#{str_japanese}/ }
153 end
153 end
154
154
155 def test_show_text_file_replace_cannot_convert_content
155 def test_show_text_file_replace_cannot_convert_content
156 set_tmp_attachments_directory
156 set_tmp_attachments_directory
157 with_settings :repositories_encodings => 'UTF-8' do
157 with_settings :repositories_encodings => 'UTF-8' do
158 a = Attachment.new(:container => Issue.find(1),
158 a = Attachment.new(:container => Issue.find(1),
159 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
159 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
160 :author => User.find(1))
160 :author => User.find(1))
161 assert a.save
161 assert a.save
162 assert_equal 'iso8859-1.txt', a.filename
162 assert_equal 'iso8859-1.txt', a.filename
163
163
164 get :show, :id => a.id
164 get :show, :id => a.id
165 assert_response :success
165 assert_response :success
166 assert_template 'file'
166 assert_template 'file'
167 assert_equal 'text/html', @response.content_type
167 assert_equal 'text/html', @response.content_type
168 assert_tag :tag => 'th',
168 assert_tag :tag => 'th',
169 :content => '7',
169 :content => '7',
170 :attributes => { :class => 'line-num' },
170 :attributes => { :class => 'line-num' },
171 :sibling => { :tag => 'td', :content => /Demande cr\?\?e avec succ\?s/ }
171 :sibling => { :tag => 'td', :content => /Demande cr\?\?e avec succ\?s/ }
172 end
172 end
173 end
173 end
174
174
175 def test_show_text_file_latin_1
175 def test_show_text_file_latin_1
176 set_tmp_attachments_directory
176 set_tmp_attachments_directory
177 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
177 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
178 a = Attachment.new(:container => Issue.find(1),
178 a = Attachment.new(:container => Issue.find(1),
179 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
179 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
180 :author => User.find(1))
180 :author => User.find(1))
181 assert a.save
181 assert a.save
182 assert_equal 'iso8859-1.txt', a.filename
182 assert_equal 'iso8859-1.txt', a.filename
183
183
184 get :show, :id => a.id
184 get :show, :id => a.id
185 assert_response :success
185 assert_response :success
186 assert_template 'file'
186 assert_template 'file'
187 assert_equal 'text/html', @response.content_type
187 assert_equal 'text/html', @response.content_type
188 assert_tag :tag => 'th',
188 assert_tag :tag => 'th',
189 :content => '7',
189 :content => '7',
190 :attributes => { :class => 'line-num' },
190 :attributes => { :class => 'line-num' },
191 :sibling => { :tag => 'td', :content => /Demande créée avec succès/ }
191 :sibling => { :tag => 'td', :content => /Demande créée avec succès/ }
192 end
192 end
193 end
193 end
194
194
195 def test_show_text_file_should_send_if_too_big
195 def test_show_text_file_should_send_if_too_big
196 Setting.file_max_size_displayed = 512
196 Setting.file_max_size_displayed = 512
197 Attachment.find(4).update_attribute :filesize, 754.kilobyte
197 Attachment.find(4).update_attribute :filesize, 754.kilobyte
198
198
199 get :show, :id => 4
199 get :show, :id => 4
200 assert_response :success
200 assert_response :success
201 assert_equal 'application/x-ruby', @response.content_type
201 assert_equal 'application/x-ruby', @response.content_type
202 set_tmp_attachments_directory
202 set_tmp_attachments_directory
203 end
203 end
204
204
205 def test_show_other
205 def test_show_other
206 get :show, :id => 6
206 get :show, :id => 6
207 assert_response :success
207 assert_response :success
208 assert_equal 'application/octet-stream', @response.content_type
208 assert_equal 'application/octet-stream', @response.content_type
209 set_tmp_attachments_directory
209 set_tmp_attachments_directory
210 end
210 end
211
211
212 def test_show_file_from_private_issue_without_permission
212 def test_show_file_from_private_issue_without_permission
213 get :show, :id => 15
213 get :show, :id => 15
214 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
214 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
215 set_tmp_attachments_directory
215 set_tmp_attachments_directory
216 end
216 end
217
217
218 def test_show_file_from_private_issue_with_permission
218 def test_show_file_from_private_issue_with_permission
219 @request.session[:user_id] = 2
219 @request.session[:user_id] = 2
220 get :show, :id => 15
220 get :show, :id => 15
221 assert_response :success
221 assert_response :success
222 assert_tag 'h2', :content => /private.diff/
222 assert_tag 'h2', :content => /private.diff/
223 set_tmp_attachments_directory
223 set_tmp_attachments_directory
224 end
224 end
225
225
226 def test_show_file_without_container_should_be_allowed_to_author
226 def test_show_file_without_container_should_be_allowed_to_author
227 set_tmp_attachments_directory
227 set_tmp_attachments_directory
228 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
228 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
229
229
230 @request.session[:user_id] = 2
230 @request.session[:user_id] = 2
231 get :show, :id => attachment.id
231 get :show, :id => attachment.id
232 assert_response 200
232 assert_response 200
233 end
233 end
234
234
235 def test_show_file_without_container_should_be_denied_to_other_users
235 def test_show_file_without_container_should_be_denied_to_other_users
236 set_tmp_attachments_directory
236 set_tmp_attachments_directory
237 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
237 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
238
238
239 @request.session[:user_id] = 3
239 @request.session[:user_id] = 3
240 get :show, :id => attachment.id
240 get :show, :id => attachment.id
241 assert_response 403
241 assert_response 403
242 end
242 end
243
243
244 def test_show_invalid_should_respond_with_404
244 def test_show_invalid_should_respond_with_404
245 get :show, :id => 999
245 get :show, :id => 999
246 assert_response 404
246 assert_response 404
247 end
247 end
248
248
249 def test_download_text_file
249 def test_download_text_file
250 get :download, :id => 4
250 get :download, :id => 4
251 assert_response :success
251 assert_response :success
252 assert_equal 'application/x-ruby', @response.content_type
252 assert_equal 'application/x-ruby', @response.content_type
253 set_tmp_attachments_directory
253 set_tmp_attachments_directory
254 end
254 end
255
255
256 def test_download_version_file_with_issue_tracking_disabled
256 def test_download_version_file_with_issue_tracking_disabled
257 Project.find(1).disable_module! :issue_tracking
257 Project.find(1).disable_module! :issue_tracking
258 get :download, :id => 9
258 get :download, :id => 9
259 assert_response :success
259 assert_response :success
260 end
260 end
261
261
262 def test_download_should_assign_content_type_if_blank
262 def test_download_should_assign_content_type_if_blank
263 Attachment.find(4).update_attribute(:content_type, '')
263 Attachment.find(4).update_attribute(:content_type, '')
264
264
265 get :download, :id => 4
265 get :download, :id => 4
266 assert_response :success
266 assert_response :success
267 assert_equal 'text/x-ruby', @response.content_type
267 assert_equal 'text/x-ruby', @response.content_type
268 set_tmp_attachments_directory
268 set_tmp_attachments_directory
269 end
269 end
270
270
271 def test_download_missing_file
271 def test_download_missing_file
272 get :download, :id => 2
272 get :download, :id => 2
273 assert_response 404
273 assert_response 404
274 set_tmp_attachments_directory
274 set_tmp_attachments_directory
275 end
275 end
276
276
277 def test_download_should_be_denied_without_permission
277 def test_download_should_be_denied_without_permission
278 get :download, :id => 7
278 get :download, :id => 7
279 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
279 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
280 set_tmp_attachments_directory
280 set_tmp_attachments_directory
281 end
281 end
282
282
283 if convert_installed?
283 if convert_installed?
284 def test_thumbnail
284 def test_thumbnail
285 Attachment.clear_thumbnails
285 Attachment.clear_thumbnails
286 @request.session[:user_id] = 2
286 @request.session[:user_id] = 2
287
287
288 get :thumbnail, :id => 16
288 get :thumbnail, :id => 16
289 assert_response :success
289 assert_response :success
290 assert_equal 'image/png', response.content_type
290 assert_equal 'image/png', response.content_type
291 end
291 end
292
292
293 def test_thumbnail_should_not_exceed_maximum_size
293 def test_thumbnail_should_not_exceed_maximum_size
294 Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 800}
294 Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 800}
295
295
296 @request.session[:user_id] = 2
296 @request.session[:user_id] = 2
297 get :thumbnail, :id => 16, :size => 2000
297 get :thumbnail, :id => 16, :size => 2000
298 end
298 end
299
299
300 def test_thumbnail_should_round_size
300 def test_thumbnail_should_round_size
301 Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 250}
301 Redmine::Thumbnail.expects(:generate).with {|source, target, size| size == 250}
302
302
303 @request.session[:user_id] = 2
303 @request.session[:user_id] = 2
304 get :thumbnail, :id => 16, :size => 260
304 get :thumbnail, :id => 16, :size => 260
305 end
305 end
306
306
307 def test_thumbnail_should_return_404_for_non_image_attachment
307 def test_thumbnail_should_return_404_for_non_image_attachment
308 @request.session[:user_id] = 2
308 @request.session[:user_id] = 2
309
309
310 get :thumbnail, :id => 15
310 get :thumbnail, :id => 15
311 assert_response 404
311 assert_response 404
312 end
312 end
313
313
314 def test_thumbnail_should_return_404_if_thumbnail_generation_failed
314 def test_thumbnail_should_return_404_if_thumbnail_generation_failed
315 Attachment.any_instance.stubs(:thumbnail).returns(nil)
315 Attachment.any_instance.stubs(:thumbnail).returns(nil)
316 @request.session[:user_id] = 2
316 @request.session[:user_id] = 2
317
317
318 get :thumbnail, :id => 16
318 get :thumbnail, :id => 16
319 assert_response 404
319 assert_response 404
320 end
320 end
321
321
322 def test_thumbnail_should_be_denied_without_permission
322 def test_thumbnail_should_be_denied_without_permission
323 get :thumbnail, :id => 16
323 get :thumbnail, :id => 16
324 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fthumbnail%2F16'
324 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fthumbnail%2F16'
325 end
325 end
326 else
326 else
327 puts '(ImageMagick convert not available)'
327 puts '(ImageMagick convert not available)'
328 end
328 end
329
329
330 def test_destroy_issue_attachment
330 def test_destroy_issue_attachment
331 set_tmp_attachments_directory
331 set_tmp_attachments_directory
332 issue = Issue.find(3)
332 issue = Issue.find(3)
333 @request.session[:user_id] = 2
333 @request.session[:user_id] = 2
334
334
335 assert_difference 'issue.attachments.count', -1 do
335 assert_difference 'issue.attachments.count', -1 do
336 assert_difference 'Journal.count' do
336 assert_difference 'Journal.count' do
337 delete :destroy, :id => 1
337 delete :destroy, :id => 1
338 assert_redirected_to '/projects/ecookbook'
338 assert_redirected_to '/projects/ecookbook'
339 end
339 end
340 end
340 end
341 assert_nil Attachment.find_by_id(1)
341 assert_nil Attachment.find_by_id(1)
342 j = Journal.order('id DESC').first
342 j = Journal.order('id DESC').first
343 assert_equal issue, j.journalized
343 assert_equal issue, j.journalized
344 assert_equal 'attachment', j.details.first.property
344 assert_equal 'attachment', j.details.first.property
345 assert_equal '1', j.details.first.prop_key
345 assert_equal '1', j.details.first.prop_key
346 assert_equal 'error281.txt', j.details.first.old_value
346 assert_equal 'error281.txt', j.details.first.old_value
347 assert_equal User.find(2), j.user
347 assert_equal User.find(2), j.user
348 end
348 end
349
349
350 def test_destroy_wiki_page_attachment
350 def test_destroy_wiki_page_attachment
351 set_tmp_attachments_directory
351 set_tmp_attachments_directory
352 @request.session[:user_id] = 2
352 @request.session[:user_id] = 2
353 assert_difference 'Attachment.count', -1 do
353 assert_difference 'Attachment.count', -1 do
354 delete :destroy, :id => 3
354 delete :destroy, :id => 3
355 assert_response 302
355 assert_response 302
356 end
356 end
357 end
357 end
358
358
359 def test_destroy_project_attachment
359 def test_destroy_project_attachment
360 set_tmp_attachments_directory
360 set_tmp_attachments_directory
361 @request.session[:user_id] = 2
361 @request.session[:user_id] = 2
362 assert_difference 'Attachment.count', -1 do
362 assert_difference 'Attachment.count', -1 do
363 delete :destroy, :id => 8
363 delete :destroy, :id => 8
364 assert_response 302
364 assert_response 302
365 end
365 end
366 end
366 end
367
367
368 def test_destroy_version_attachment
368 def test_destroy_version_attachment
369 set_tmp_attachments_directory
369 set_tmp_attachments_directory
370 @request.session[:user_id] = 2
370 @request.session[:user_id] = 2
371 assert_difference 'Attachment.count', -1 do
371 assert_difference 'Attachment.count', -1 do
372 delete :destroy, :id => 9
372 delete :destroy, :id => 9
373 assert_response 302
373 assert_response 302
374 end
374 end
375 end
375 end
376
376
377 def test_destroy_version_attachment_with_issue_tracking_disabled
378 Project.find(1).disable_module! :issue_tracking
379 set_tmp_attachments_directory
380 @request.session[:user_id] = 2
381 assert_difference 'Attachment.count', -1 do
382 delete :destroy, :id => 9
383 assert_response 302
384 end
385 end
386
377 def test_destroy_without_permission
387 def test_destroy_without_permission
378 set_tmp_attachments_directory
388 set_tmp_attachments_directory
379 assert_no_difference 'Attachment.count' do
389 assert_no_difference 'Attachment.count' do
380 delete :destroy, :id => 3
390 delete :destroy, :id => 3
381 end
391 end
382 assert_response 302
392 assert_response 302
383 assert Attachment.find_by_id(3)
393 assert Attachment.find_by_id(3)
384 end
394 end
385 end
395 end
General Comments 0
You need to be logged in to leave comments. Login now