##// END OF EJS Templates
Merged r7983 and r7984 from trunk....
Jean-Philippe Lang -
r7880:fa894328bb94
parent child
Show More
@@ -1,234 +1,239
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 after_update :update_issues_from_sharing_change
19 after_update :update_issues_from_sharing_change
20 belongs_to :project
20 belongs_to :project
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
22 acts_as_customizable
22 acts_as_customizable
23 acts_as_attachable :view_permission => :view_files,
23 acts_as_attachable :view_permission => :view_files,
24 :delete_permission => :manage_files
24 :delete_permission => :manage_files
25
25
26 VERSION_STATUSES = %w(open locked closed)
26 VERSION_STATUSES = %w(open locked closed)
27 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
27 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
28
28
29 validates_presence_of :name
29 validates_presence_of :name
30 validates_uniqueness_of :name, :scope => [:project_id]
30 validates_uniqueness_of :name, :scope => [:project_id]
31 validates_length_of :name, :maximum => 60
31 validates_length_of :name, :maximum => 60
32 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
32 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
33 validates_inclusion_of :status, :in => VERSION_STATUSES
33 validates_inclusion_of :status, :in => VERSION_STATUSES
34 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
34 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
35
35
36 named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
36 named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
37 named_scope :open, :conditions => {:status => 'open'}
37 named_scope :open, :conditions => {:status => 'open'}
38 named_scope :visible, lambda {|*args| { :include => :project,
38 named_scope :visible, lambda {|*args| { :include => :project,
39 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
39 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
40
40
41 # Returns true if +user+ or current user is allowed to view the version
41 # Returns true if +user+ or current user is allowed to view the version
42 def visible?(user=User.current)
42 def visible?(user=User.current)
43 user.allowed_to?(:view_issues, self.project)
43 user.allowed_to?(:view_issues, self.project)
44 end
44 end
45
45
46 # Version files have same visibility as project files
47 def attachments_visible?(*args)
48 project.present? && project.attachments_visible?(*args)
49 end
50
46 def start_date
51 def start_date
47 @start_date ||= fixed_issues.minimum('start_date')
52 @start_date ||= fixed_issues.minimum('start_date')
48 end
53 end
49
54
50 def due_date
55 def due_date
51 effective_date
56 effective_date
52 end
57 end
53
58
54 # Returns the total estimated time for this version
59 # Returns the total estimated time for this version
55 # (sum of leaves estimated_hours)
60 # (sum of leaves estimated_hours)
56 def estimated_hours
61 def estimated_hours
57 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
62 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
58 end
63 end
59
64
60 # Returns the total reported time for this version
65 # Returns the total reported time for this version
61 def spent_hours
66 def spent_hours
62 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
67 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
63 end
68 end
64
69
65 def closed?
70 def closed?
66 status == 'closed'
71 status == 'closed'
67 end
72 end
68
73
69 def open?
74 def open?
70 status == 'open'
75 status == 'open'
71 end
76 end
72
77
73 # Returns true if the version is completed: due date reached and no open issues
78 # Returns true if the version is completed: due date reached and no open issues
74 def completed?
79 def completed?
75 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
80 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
76 end
81 end
77
82
78 def behind_schedule?
83 def behind_schedule?
79 if completed_pourcent == 100
84 if completed_pourcent == 100
80 return false
85 return false
81 elsif due_date && start_date
86 elsif due_date && start_date
82 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
87 done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
83 return done_date <= Date.today
88 return done_date <= Date.today
84 else
89 else
85 false # No issues so it's not late
90 false # No issues so it's not late
86 end
91 end
87 end
92 end
88
93
89 # Returns the completion percentage of this version based on the amount of open/closed issues
94 # Returns the completion percentage of this version based on the amount of open/closed issues
90 # and the time spent on the open issues.
95 # and the time spent on the open issues.
91 def completed_pourcent
96 def completed_pourcent
92 if issues_count == 0
97 if issues_count == 0
93 0
98 0
94 elsif open_issues_count == 0
99 elsif open_issues_count == 0
95 100
100 100
96 else
101 else
97 issues_progress(false) + issues_progress(true)
102 issues_progress(false) + issues_progress(true)
98 end
103 end
99 end
104 end
100
105
101 # Returns the percentage of issues that have been marked as 'closed'.
106 # Returns the percentage of issues that have been marked as 'closed'.
102 def closed_pourcent
107 def closed_pourcent
103 if issues_count == 0
108 if issues_count == 0
104 0
109 0
105 else
110 else
106 issues_progress(false)
111 issues_progress(false)
107 end
112 end
108 end
113 end
109
114
110 # Returns true if the version is overdue: due date reached and some open issues
115 # Returns true if the version is overdue: due date reached and some open issues
111 def overdue?
116 def overdue?
112 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
117 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
113 end
118 end
114
119
115 # Returns assigned issues count
120 # Returns assigned issues count
116 def issues_count
121 def issues_count
117 @issue_count ||= fixed_issues.count
122 @issue_count ||= fixed_issues.count
118 end
123 end
119
124
120 # Returns the total amount of open issues for this version.
125 # Returns the total amount of open issues for this version.
121 def open_issues_count
126 def open_issues_count
122 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
127 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
123 end
128 end
124
129
125 # Returns the total amount of closed issues for this version.
130 # Returns the total amount of closed issues for this version.
126 def closed_issues_count
131 def closed_issues_count
127 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
132 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
128 end
133 end
129
134
130 def wiki_page
135 def wiki_page
131 if project.wiki && !wiki_page_title.blank?
136 if project.wiki && !wiki_page_title.blank?
132 @wiki_page ||= project.wiki.find_page(wiki_page_title)
137 @wiki_page ||= project.wiki.find_page(wiki_page_title)
133 end
138 end
134 @wiki_page
139 @wiki_page
135 end
140 end
136
141
137 def to_s; name end
142 def to_s; name end
138
143
139 def to_s_with_project
144 def to_s_with_project
140 "#{project} - #{name}"
145 "#{project} - #{name}"
141 end
146 end
142
147
143 # Versions are sorted by effective_date and "Project Name - Version name"
148 # Versions are sorted by effective_date and "Project Name - Version name"
144 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
149 # Those with no effective_date are at the end, sorted by "Project Name - Version name"
145 def <=>(version)
150 def <=>(version)
146 if self.effective_date
151 if self.effective_date
147 if version.effective_date
152 if version.effective_date
148 if self.effective_date == version.effective_date
153 if self.effective_date == version.effective_date
149 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
154 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
150 else
155 else
151 self.effective_date <=> version.effective_date
156 self.effective_date <=> version.effective_date
152 end
157 end
153 else
158 else
154 -1
159 -1
155 end
160 end
156 else
161 else
157 if version.effective_date
162 if version.effective_date
158 1
163 1
159 else
164 else
160 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
165 "#{self.project.name} - #{self.name}" <=> "#{version.project.name} - #{version.name}"
161 end
166 end
162 end
167 end
163 end
168 end
164
169
165 # Returns the sharings that +user+ can set the version to
170 # Returns the sharings that +user+ can set the version to
166 def allowed_sharings(user = User.current)
171 def allowed_sharings(user = User.current)
167 VERSION_SHARINGS.select do |s|
172 VERSION_SHARINGS.select do |s|
168 if sharing == s
173 if sharing == s
169 true
174 true
170 else
175 else
171 case s
176 case s
172 when 'system'
177 when 'system'
173 # Only admin users can set a systemwide sharing
178 # Only admin users can set a systemwide sharing
174 user.admin?
179 user.admin?
175 when 'hierarchy', 'tree'
180 when 'hierarchy', 'tree'
176 # Only users allowed to manage versions of the root project can
181 # Only users allowed to manage versions of the root project can
177 # set sharing to hierarchy or tree
182 # set sharing to hierarchy or tree
178 project.nil? || user.allowed_to?(:manage_versions, project.root)
183 project.nil? || user.allowed_to?(:manage_versions, project.root)
179 else
184 else
180 true
185 true
181 end
186 end
182 end
187 end
183 end
188 end
184 end
189 end
185
190
186 private
191 private
187
192
188 # Update the issue's fixed versions. Used if a version's sharing changes.
193 # Update the issue's fixed versions. Used if a version's sharing changes.
189 def update_issues_from_sharing_change
194 def update_issues_from_sharing_change
190 if sharing_changed?
195 if sharing_changed?
191 if VERSION_SHARINGS.index(sharing_was).nil? ||
196 if VERSION_SHARINGS.index(sharing_was).nil? ||
192 VERSION_SHARINGS.index(sharing).nil? ||
197 VERSION_SHARINGS.index(sharing).nil? ||
193 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
198 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
194 Issue.update_versions_from_sharing_change self
199 Issue.update_versions_from_sharing_change self
195 end
200 end
196 end
201 end
197 end
202 end
198
203
199 # Returns the average estimated time of assigned issues
204 # Returns the average estimated time of assigned issues
200 # or 1 if no issue has an estimated time
205 # or 1 if no issue has an estimated time
201 # Used to weigth unestimated issues in progress calculation
206 # Used to weigth unestimated issues in progress calculation
202 def estimated_average
207 def estimated_average
203 if @estimated_average.nil?
208 if @estimated_average.nil?
204 average = fixed_issues.average(:estimated_hours).to_f
209 average = fixed_issues.average(:estimated_hours).to_f
205 if average == 0
210 if average == 0
206 average = 1
211 average = 1
207 end
212 end
208 @estimated_average = average
213 @estimated_average = average
209 end
214 end
210 @estimated_average
215 @estimated_average
211 end
216 end
212
217
213 # Returns the total progress of open or closed issues. The returned percentage takes into account
218 # Returns the total progress of open or closed issues. The returned percentage takes into account
214 # the amount of estimated time set for this version.
219 # the amount of estimated time set for this version.
215 #
220 #
216 # Examples:
221 # Examples:
217 # issues_progress(true) => returns the progress percentage for open issues.
222 # issues_progress(true) => returns the progress percentage for open issues.
218 # issues_progress(false) => returns the progress percentage for closed issues.
223 # issues_progress(false) => returns the progress percentage for closed issues.
219 def issues_progress(open)
224 def issues_progress(open)
220 @issues_progress ||= {}
225 @issues_progress ||= {}
221 @issues_progress[open] ||= begin
226 @issues_progress[open] ||= begin
222 progress = 0
227 progress = 0
223 if issues_count > 0
228 if issues_count > 0
224 ratio = open ? 'done_ratio' : 100
229 ratio = open ? 'done_ratio' : 100
225
230
226 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
231 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
227 :include => :status,
232 :include => :status,
228 :conditions => ["is_closed = ?", !open]).to_f
233 :conditions => ["is_closed = ?", !open]).to_f
229 progress = done / (estimated_average * issues_count)
234 progress = done / (estimated_average * issues_count)
230 end
235 end
231 progress
236 progress
232 end
237 end
233 end
238 end
234 end
239 end
@@ -1,184 +1,184
1 ---
1 ---
2 attachments_001:
2 attachments_001:
3 created_on: 2006-07-19 21:07:27 +02:00
3 created_on: 2006-07-19 21:07:27 +02:00
4 downloads: 0
4 downloads: 0
5 content_type: text/plain
5 content_type: text/plain
6 disk_filename: 060719210727_error281.txt
6 disk_filename: 060719210727_error281.txt
7 container_id: 3
7 container_id: 3
8 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
8 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
9 id: 1
9 id: 1
10 container_type: Issue
10 container_type: Issue
11 filesize: 28
11 filesize: 28
12 filename: error281.txt
12 filename: error281.txt
13 author_id: 2
13 author_id: 2
14 attachments_002:
14 attachments_002:
15 created_on: 2007-01-27 15:08:27 +01:00
15 created_on: 2007-01-27 15:08:27 +01:00
16 downloads: 0
16 downloads: 0
17 content_type: text/plain
17 content_type: text/plain
18 disk_filename: 060719210727_document.txt
18 disk_filename: 060719210727_document.txt
19 container_id: 1
19 container_id: 1
20 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
20 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
21 id: 2
21 id: 2
22 container_type: Document
22 container_type: Document
23 filesize: 28
23 filesize: 28
24 filename: document.txt
24 filename: document.txt
25 author_id: 2
25 author_id: 2
26 attachments_003:
26 attachments_003:
27 created_on: 2006-07-19 21:07:27 +02:00
27 created_on: 2006-07-19 21:07:27 +02:00
28 downloads: 0
28 downloads: 0
29 content_type: image/gif
29 content_type: image/gif
30 disk_filename: 060719210727_logo.gif
30 disk_filename: 060719210727_logo.gif
31 container_id: 4
31 container_id: 4
32 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
32 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
33 id: 3
33 id: 3
34 container_type: WikiPage
34 container_type: WikiPage
35 filesize: 280
35 filesize: 280
36 filename: logo.gif
36 filename: logo.gif
37 description: This is a logo
37 description: This is a logo
38 author_id: 2
38 author_id: 2
39 attachments_004:
39 attachments_004:
40 created_on: 2006-07-19 21:07:27 +02:00
40 created_on: 2006-07-19 21:07:27 +02:00
41 container_type: Issue
41 container_type: Issue
42 container_id: 3
42 container_id: 3
43 downloads: 0
43 downloads: 0
44 disk_filename: 060719210727_source.rb
44 disk_filename: 060719210727_source.rb
45 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
45 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
46 id: 4
46 id: 4
47 filesize: 153
47 filesize: 153
48 filename: source.rb
48 filename: source.rb
49 author_id: 2
49 author_id: 2
50 description: This is a Ruby source file
50 description: This is a Ruby source file
51 content_type: application/x-ruby
51 content_type: application/x-ruby
52 attachments_005:
52 attachments_005:
53 created_on: 2006-07-19 21:07:27 +02:00
53 created_on: 2006-07-19 21:07:27 +02:00
54 container_type: Issue
54 container_type: Issue
55 container_id: 3
55 container_id: 3
56 downloads: 0
56 downloads: 0
57 disk_filename: 060719210727_changeset_iso8859-1.diff
57 disk_filename: 060719210727_changeset_iso8859-1.diff
58 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
58 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
59 id: 5
59 id: 5
60 filesize: 687
60 filesize: 687
61 filename: changeset_iso8859-1.diff
61 filename: changeset_iso8859-1.diff
62 author_id: 2
62 author_id: 2
63 content_type: text/x-diff
63 content_type: text/x-diff
64 attachments_006:
64 attachments_006:
65 created_on: 2006-07-19 21:07:27 +02:00
65 created_on: 2006-07-19 21:07:27 +02:00
66 container_type: Issue
66 container_type: Issue
67 container_id: 3
67 container_id: 3
68 downloads: 0
68 downloads: 0
69 disk_filename: 060719210727_archive.zip
69 disk_filename: 060719210727_archive.zip
70 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
70 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
71 id: 6
71 id: 6
72 filesize: 157
72 filesize: 157
73 filename: archive.zip
73 filename: archive.zip
74 author_id: 2
74 author_id: 2
75 content_type: application/octet-stream
75 content_type: application/octet-stream
76 attachments_007:
76 attachments_007:
77 created_on: 2006-07-19 21:07:27 +02:00
77 created_on: 2006-07-19 21:07:27 +02:00
78 container_type: Issue
78 container_type: Issue
79 container_id: 4
79 container_id: 4
80 downloads: 0
80 downloads: 0
81 disk_filename: 060719210727_archive.zip
81 disk_filename: 060719210727_archive.zip
82 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
82 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
83 id: 7
83 id: 7
84 filesize: 157
84 filesize: 157
85 filename: archive.zip
85 filename: archive.zip
86 author_id: 1
86 author_id: 1
87 content_type: application/octet-stream
87 content_type: application/octet-stream
88 attachments_008:
88 attachments_008:
89 created_on: 2006-07-19 21:07:27 +02:00
89 created_on: 2006-07-19 21:07:27 +02:00
90 container_type: Project
90 container_type: Project
91 container_id: 1
91 container_id: 1
92 downloads: 0
92 downloads: 0
93 disk_filename: 060719210727_project_file.zip
93 disk_filename: 060719210727_project_file.zip
94 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
94 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
95 id: 8
95 id: 8
96 filesize: 320
96 filesize: 320
97 filename: project_file.zip
97 filename: project_file.zip
98 author_id: 2
98 author_id: 2
99 content_type: application/octet-stream
99 content_type: application/octet-stream
100 attachments_009:
100 attachments_009:
101 created_on: 2006-07-19 21:07:27 +02:00
101 created_on: 2006-07-19 21:07:27 +02:00
102 container_type: Version
102 container_type: Version
103 container_id: 1
103 container_id: 1
104 downloads: 0
104 downloads: 0
105 disk_filename: 060719210727_version_file.zip
105 disk_filename: 060719210727_archive.zip
106 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
106 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
107 id: 9
107 id: 9
108 filesize: 452
108 filesize: 452
109 filename: version_file.zip
109 filename: version_file.zip
110 author_id: 2
110 author_id: 2
111 content_type: application/octet-stream
111 content_type: application/octet-stream
112 attachments_010:
112 attachments_010:
113 created_on: 2006-07-19 21:07:27 +02:00
113 created_on: 2006-07-19 21:07:27 +02:00
114 container_type: Issue
114 container_type: Issue
115 container_id: 2
115 container_id: 2
116 downloads: 0
116 downloads: 0
117 disk_filename: 060719210727_picture.jpg
117 disk_filename: 060719210727_picture.jpg
118 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
118 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
119 id: 10
119 id: 10
120 filesize: 452
120 filesize: 452
121 filename: picture.jpg
121 filename: picture.jpg
122 author_id: 2
122 author_id: 2
123 content_type: image/jpeg
123 content_type: image/jpeg
124 attachments_011:
124 attachments_011:
125 created_on: 2007-02-12 15:08:27 +01:00
125 created_on: 2007-02-12 15:08:27 +01:00
126 container_type: Document
126 container_type: Document
127 container_id: 1
127 container_id: 1
128 downloads: 0
128 downloads: 0
129 disk_filename: 060719210727_picture.jpg
129 disk_filename: 060719210727_picture.jpg
130 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
130 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
131 id: 11
131 id: 11
132 filesize: 452
132 filesize: 452
133 filename: picture.jpg
133 filename: picture.jpg
134 author_id: 2
134 author_id: 2
135 content_type: image/jpeg
135 content_type: image/jpeg
136 attachments_012:
136 attachments_012:
137 created_on: 2006-07-19 21:07:27 +02:00
137 created_on: 2006-07-19 21:07:27 +02:00
138 container_type: Version
138 container_type: Version
139 container_id: 1
139 container_id: 1
140 downloads: 0
140 downloads: 0
141 disk_filename: 060719210727_version_file.zip
141 disk_filename: 060719210727_version_file.zip
142 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
142 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
143 id: 12
143 id: 12
144 filesize: 452
144 filesize: 452
145 filename: version_file.zip
145 filename: version_file.zip
146 author_id: 2
146 author_id: 2
147 content_type: application/octet-stream
147 content_type: application/octet-stream
148 attachments_013:
148 attachments_013:
149 created_on: 2006-07-19 21:07:27 +02:00
149 created_on: 2006-07-19 21:07:27 +02:00
150 container_type: Message
150 container_type: Message
151 container_id: 1
151 container_id: 1
152 downloads: 0
152 downloads: 0
153 disk_filename: 060719210727_foo.zip
153 disk_filename: 060719210727_foo.zip
154 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
154 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
155 id: 13
155 id: 13
156 filesize: 452
156 filesize: 452
157 filename: foo.zip
157 filename: foo.zip
158 author_id: 2
158 author_id: 2
159 content_type: application/octet-stream
159 content_type: application/octet-stream
160 attachments_014:
160 attachments_014:
161 created_on: 2006-07-19 21:07:27 +02:00
161 created_on: 2006-07-19 21:07:27 +02:00
162 container_type: Issue
162 container_type: Issue
163 container_id: 3
163 container_id: 3
164 downloads: 0
164 downloads: 0
165 disk_filename: 060719210727_changeset_utf8.diff
165 disk_filename: 060719210727_changeset_utf8.diff
166 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
166 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
167 id: 14
167 id: 14
168 filesize: 687
168 filesize: 687
169 filename: changeset_utf8.diff
169 filename: changeset_utf8.diff
170 author_id: 2
170 author_id: 2
171 content_type: text/x-diff
171 content_type: text/x-diff
172 attachments_015:
172 attachments_015:
173 id: 15
173 id: 15
174 created_on: 2010-07-19 21:07:27 +02:00
174 created_on: 2010-07-19 21:07:27 +02:00
175 container_type: Issue
175 container_type: Issue
176 container_id: 14
176 container_id: 14
177 downloads: 0
177 downloads: 0
178 disk_filename: 060719210727_changeset_utf8.diff
178 disk_filename: 060719210727_changeset_utf8.diff
179 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
179 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
180 filesize: 687
180 filesize: 687
181 filename: private.diff
181 filename: private.diff
182 author_id: 2
182 author_id: 2
183 content_type: text/x-diff
183 content_type: text/x-diff
184 description: attachement of a private issue
184 description: attachement of a private issue
@@ -1,170 +1,183
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 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 require 'attachments_controller'
21 require 'attachments_controller'
22
22
23 # Re-raise errors caught by the controller.
23 # Re-raise errors caught by the controller.
24 class AttachmentsController; def rescue_action(e) raise e end; end
24 class AttachmentsController; def rescue_action(e) raise e end; end
25
25
26
26
27 class AttachmentsControllerTest < ActionController::TestCase
27 class AttachmentsControllerTest < ActionController::TestCase
28 fixtures :users, :projects, :roles, :members, :member_roles, :enabled_modules, :issues, :trackers, :attachments,
28 fixtures :users, :projects, :roles, :members, :member_roles, :enabled_modules, :issues, :trackers, :attachments,
29 :versions, :wiki_pages, :wikis, :documents
29 :versions, :wiki_pages, :wikis, :documents
30
30
31 def setup
31 def setup
32 @controller = AttachmentsController.new
32 @controller = AttachmentsController.new
33 @request = ActionController::TestRequest.new
33 @request = ActionController::TestRequest.new
34 @response = ActionController::TestResponse.new
34 @response = ActionController::TestResponse.new
35 Attachment.storage_path = "#{RAILS_ROOT}/test/fixtures/files"
35 Attachment.storage_path = "#{RAILS_ROOT}/test/fixtures/files"
36 User.current = nil
36 User.current = nil
37 end
37 end
38
38
39 def test_show_diff
39 def test_show_diff
40 get :show, :id => 14 # 060719210727_changeset_utf8.diff
40 get :show, :id => 14 # 060719210727_changeset_utf8.diff
41 assert_response :success
41 assert_response :success
42 assert_template 'diff'
42 assert_template 'diff'
43 assert_equal 'text/html', @response.content_type
43 assert_equal 'text/html', @response.content_type
44
44
45 assert_tag 'th',
45 assert_tag 'th',
46 :attributes => {:class => /filename/},
46 :attributes => {:class => /filename/},
47 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
47 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
48 assert_tag 'td',
48 assert_tag 'td',
49 :attributes => {:class => /line-code/},
49 :attributes => {:class => /line-code/},
50 :content => /Demande créée avec succès/
50 :content => /Demande créée avec succès/
51 end
51 end
52
52
53 def test_show_diff_should_strip_non_utf8_content
53 def test_show_diff_should_strip_non_utf8_content
54 get :show, :id => 5 # 060719210727_changeset_iso8859-1.diff
54 get :show, :id => 5 # 060719210727_changeset_iso8859-1.diff
55 assert_response :success
55 assert_response :success
56 assert_template 'diff'
56 assert_template 'diff'
57 assert_equal 'text/html', @response.content_type
57 assert_equal 'text/html', @response.content_type
58
58
59 assert_tag 'th',
59 assert_tag 'th',
60 :attributes => {:class => /filename/},
60 :attributes => {:class => /filename/},
61 :content => /issues_controller.rb\t\(rvision 1484\)/
61 :content => /issues_controller.rb\t\(rvision 1484\)/
62 assert_tag 'td',
62 assert_tag 'td',
63 :attributes => {:class => /line-code/},
63 :attributes => {:class => /line-code/},
64 :content => /Demande cre avec succs/
64 :content => /Demande cre avec succs/
65 end
65 end
66
66
67 def test_show_text_file
67 def test_show_text_file
68 get :show, :id => 4
68 get :show, :id => 4
69 assert_response :success
69 assert_response :success
70 assert_template 'file'
70 assert_template 'file'
71 assert_equal 'text/html', @response.content_type
71 assert_equal 'text/html', @response.content_type
72 end
72 end
73
73
74 def test_show_text_file_should_send_if_too_big
74 def test_show_text_file_should_send_if_too_big
75 Setting.file_max_size_displayed = 512
75 Setting.file_max_size_displayed = 512
76 Attachment.find(4).update_attribute :filesize, 754.kilobyte
76 Attachment.find(4).update_attribute :filesize, 754.kilobyte
77
77
78 get :show, :id => 4
78 get :show, :id => 4
79 assert_response :success
79 assert_response :success
80 assert_equal 'application/x-ruby', @response.content_type
80 assert_equal 'application/x-ruby', @response.content_type
81 end
81 end
82
82
83 def test_show_other
83 def test_show_other
84 get :show, :id => 6
84 get :show, :id => 6
85 assert_response :success
85 assert_response :success
86 assert_equal 'application/octet-stream', @response.content_type
86 assert_equal 'application/octet-stream', @response.content_type
87 end
87 end
88
88
89 def test_show_file_from_private_issue_without_permission
89 def test_show_file_from_private_issue_without_permission
90 get :show, :id => 15
90 get :show, :id => 15
91 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
91 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
92 end
92 end
93
93
94 def test_show_file_from_private_issue_with_permission
94 def test_show_file_from_private_issue_with_permission
95 @request.session[:user_id] = 2
95 @request.session[:user_id] = 2
96 get :show, :id => 15
96 get :show, :id => 15
97 assert_response :success
97 assert_response :success
98 assert_tag 'h2', :content => /private.diff/
98 assert_tag 'h2', :content => /private.diff/
99 end
99 end
100
100
101 def test_download_text_file
101 def test_download_text_file
102 get :download, :id => 4
102 get :download, :id => 4
103 assert_response :success
103 assert_response :success
104 assert_equal 'application/x-ruby', @response.content_type
104 assert_equal 'application/x-ruby', @response.content_type
105 end
105 end
106
106
107 def test_download_version_file_with_issue_tracking_disabled
108 Project.find(1).disable_module! :issue_tracking
109 get :download, :id => 9
110 assert_response :success
111 end
112
107 def test_download_should_assign_content_type_if_blank
113 def test_download_should_assign_content_type_if_blank
108 Attachment.find(4).update_attribute(:content_type, '')
114 Attachment.find(4).update_attribute(:content_type, '')
109
115
110 get :download, :id => 4
116 get :download, :id => 4
111 assert_response :success
117 assert_response :success
112 assert_equal 'text/x-ruby', @response.content_type
118 assert_equal 'text/x-ruby', @response.content_type
113 end
119 end
114
120
115 def test_download_missing_file
121 def test_download_missing_file
116 get :download, :id => 2
122 get :download, :id => 2
117 assert_response 404
123 assert_response 404
118 end
124 end
119
125
120 def test_anonymous_on_private_private
126 def test_anonymous_on_private_private
121 get :download, :id => 7
127 get :download, :id => 7
122 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
128 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
123 end
129 end
124
130
125 def test_destroy_issue_attachment
131 def test_destroy_issue_attachment
132 set_tmp_attachments_directory
126 issue = Issue.find(3)
133 issue = Issue.find(3)
127 @request.session[:user_id] = 2
134 @request.session[:user_id] = 2
128
135
129 assert_difference 'issue.attachments.count', -1 do
136 assert_difference 'issue.attachments.count', -1 do
130 post :destroy, :id => 1
137 post :destroy, :id => 1
131 end
138 end
132 # no referrer
139 # no referrer
133 assert_redirected_to '/projects/ecookbook'
140 assert_redirected_to '/projects/ecookbook'
134 assert_nil Attachment.find_by_id(1)
141 assert_nil Attachment.find_by_id(1)
135 j = issue.journals.find(:first, :order => 'created_on DESC')
142 j = issue.journals.find(:first, :order => 'created_on DESC')
136 assert_equal 'attachment', j.details.first.property
143 assert_equal 'attachment', j.details.first.property
137 assert_equal '1', j.details.first.prop_key
144 assert_equal '1', j.details.first.prop_key
138 assert_equal 'error281.txt', j.details.first.old_value
145 assert_equal 'error281.txt', j.details.first.old_value
139 end
146 end
140
147
141 def test_destroy_wiki_page_attachment
148 def test_destroy_wiki_page_attachment
149 set_tmp_attachments_directory
142 @request.session[:user_id] = 2
150 @request.session[:user_id] = 2
143 assert_difference 'Attachment.count', -1 do
151 assert_difference 'Attachment.count', -1 do
144 post :destroy, :id => 3
152 post :destroy, :id => 3
145 assert_response 302
153 assert_response 302
146 end
154 end
147 end
155 end
148
156
149 def test_destroy_project_attachment
157 def test_destroy_project_attachment
158 set_tmp_attachments_directory
150 @request.session[:user_id] = 2
159 @request.session[:user_id] = 2
151 assert_difference 'Attachment.count', -1 do
160 assert_difference 'Attachment.count', -1 do
152 post :destroy, :id => 8
161 post :destroy, :id => 8
153 assert_response 302
162 assert_response 302
154 end
163 end
155 end
164 end
156
165
157 def test_destroy_version_attachment
166 def test_destroy_version_attachment
167 set_tmp_attachments_directory
158 @request.session[:user_id] = 2
168 @request.session[:user_id] = 2
159 assert_difference 'Attachment.count', -1 do
169 assert_difference 'Attachment.count', -1 do
160 post :destroy, :id => 9
170 post :destroy, :id => 9
161 assert_response 302
171 assert_response 302
162 end
172 end
163 end
173 end
164
174
165 def test_destroy_without_permission
175 def test_destroy_without_permission
166 post :destroy, :id => 3
176 set_tmp_attachments_directory
167 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdestroy%2F3'
177 assert_no_difference 'Attachment.count' do
178 delete :destroy, :id => 3
179 end
180 assert_response 302
168 assert Attachment.find_by_id(3)
181 assert Attachment.find_by_id(3)
169 end
182 end
170 end
183 end
General Comments 0
You need to be logged in to leave comments. Login now