##// END OF EJS Templates
Make sure that tests restore the attachments path to the tmp dir so that fixture files don't get deleted....
Jean-Philippe Lang -
r8128:ad25e3807d2d
parent child
Show More
@@ -1,280 +1,284
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 class AttachmentsControllerTest < ActionController::TestCase
26 class AttachmentsControllerTest < ActionController::TestCase
27 fixtures :users, :projects, :roles, :members, :member_roles,
27 fixtures :users, :projects, :roles, :members, :member_roles,
28 :enabled_modules, :issues, :trackers, :attachments,
28 :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"
36 User.current = nil
35 User.current = nil
36 set_fixtures_attachments_directory
37 end
38
39 def teardown
40 set_tmp_attachments_directory
37 end
41 end
38
42
39 def test_show_diff
43 def test_show_diff
40 ['inline', 'sbs'].each do |dt|
44 ['inline', 'sbs'].each do |dt|
41 # 060719210727_changeset_utf8.diff
45 # 060719210727_changeset_utf8.diff
42 get :show, :id => 14, :type => dt
46 get :show, :id => 14, :type => dt
43 assert_response :success
47 assert_response :success
44 assert_template 'diff'
48 assert_template 'diff'
45 assert_equal 'text/html', @response.content_type
49 assert_equal 'text/html', @response.content_type
46 assert_tag 'th',
50 assert_tag 'th',
47 :attributes => {:class => /filename/},
51 :attributes => {:class => /filename/},
48 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
52 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
49 assert_tag 'td',
53 assert_tag 'td',
50 :attributes => {:class => /line-code/},
54 :attributes => {:class => /line-code/},
51 :content => /Demande créée avec succès/
55 :content => /Demande créée avec succès/
52 end
56 end
53 set_tmp_attachments_directory
57 set_tmp_attachments_directory
54 end
58 end
55
59
56 def test_show_diff_replcace_cannot_convert_content
60 def test_show_diff_replcace_cannot_convert_content
57 with_settings :repositories_encodings => 'UTF-8' do
61 with_settings :repositories_encodings => 'UTF-8' do
58 ['inline', 'sbs'].each do |dt|
62 ['inline', 'sbs'].each do |dt|
59 # 060719210727_changeset_iso8859-1.diff
63 # 060719210727_changeset_iso8859-1.diff
60 get :show, :id => 5, :type => dt
64 get :show, :id => 5, :type => dt
61 assert_response :success
65 assert_response :success
62 assert_template 'diff'
66 assert_template 'diff'
63 assert_equal 'text/html', @response.content_type
67 assert_equal 'text/html', @response.content_type
64 assert_tag 'th',
68 assert_tag 'th',
65 :attributes => {:class => "filename"},
69 :attributes => {:class => "filename"},
66 :content => /issues_controller.rb\t\(r\?vision 1484\)/
70 :content => /issues_controller.rb\t\(r\?vision 1484\)/
67 assert_tag 'td',
71 assert_tag 'td',
68 :attributes => {:class => /line-code/},
72 :attributes => {:class => /line-code/},
69 :content => /Demande cr\?\?e avec succ\?s/
73 :content => /Demande cr\?\?e avec succ\?s/
70 end
74 end
71 end
75 end
72 set_tmp_attachments_directory
76 set_tmp_attachments_directory
73 end
77 end
74
78
75 def test_show_diff_latin_1
79 def test_show_diff_latin_1
76 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
80 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
77 ['inline', 'sbs'].each do |dt|
81 ['inline', 'sbs'].each do |dt|
78 # 060719210727_changeset_iso8859-1.diff
82 # 060719210727_changeset_iso8859-1.diff
79 get :show, :id => 5, :type => dt
83 get :show, :id => 5, :type => dt
80 assert_response :success
84 assert_response :success
81 assert_template 'diff'
85 assert_template 'diff'
82 assert_equal 'text/html', @response.content_type
86 assert_equal 'text/html', @response.content_type
83 assert_tag 'th',
87 assert_tag 'th',
84 :attributes => {:class => "filename"},
88 :attributes => {:class => "filename"},
85 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
89 :content => /issues_controller.rb\t\(rΓ©vision 1484\)/
86 assert_tag 'td',
90 assert_tag 'td',
87 :attributes => {:class => /line-code/},
91 :attributes => {:class => /line-code/},
88 :content => /Demande créée avec succès/
92 :content => /Demande créée avec succès/
89 end
93 end
90 end
94 end
91 set_tmp_attachments_directory
95 set_tmp_attachments_directory
92 end
96 end
93
97
94 def test_show_text_file
98 def test_show_text_file
95 get :show, :id => 4
99 get :show, :id => 4
96 assert_response :success
100 assert_response :success
97 assert_template 'file'
101 assert_template 'file'
98 assert_equal 'text/html', @response.content_type
102 assert_equal 'text/html', @response.content_type
99 set_tmp_attachments_directory
103 set_tmp_attachments_directory
100 end
104 end
101
105
102 def test_show_text_file_utf_8
106 def test_show_text_file_utf_8
103 set_tmp_attachments_directory
107 set_tmp_attachments_directory
104 a = Attachment.new(:container => Issue.find(1),
108 a = Attachment.new(:container => Issue.find(1),
105 :file => uploaded_test_file("japanese-utf-8.txt", "text/plain"),
109 :file => uploaded_test_file("japanese-utf-8.txt", "text/plain"),
106 :author => User.find(1))
110 :author => User.find(1))
107 assert a.save
111 assert a.save
108 assert_equal 'japanese-utf-8.txt', a.filename
112 assert_equal 'japanese-utf-8.txt', a.filename
109
113
110 str_japanese = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
114 str_japanese = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
111 str_japanese.force_encoding('UTF-8') if str_japanese.respond_to?(:force_encoding)
115 str_japanese.force_encoding('UTF-8') if str_japanese.respond_to?(:force_encoding)
112
116
113 get :show, :id => a.id
117 get :show, :id => a.id
114 assert_response :success
118 assert_response :success
115 assert_template 'file'
119 assert_template 'file'
116 assert_equal 'text/html', @response.content_type
120 assert_equal 'text/html', @response.content_type
117 assert_tag :tag => 'th',
121 assert_tag :tag => 'th',
118 :content => '1',
122 :content => '1',
119 :attributes => { :class => 'line-num' },
123 :attributes => { :class => 'line-num' },
120 :sibling => { :tag => 'td', :content => /#{str_japanese}/ }
124 :sibling => { :tag => 'td', :content => /#{str_japanese}/ }
121 end
125 end
122
126
123 def test_show_text_file_replcace_cannot_convert_content
127 def test_show_text_file_replcace_cannot_convert_content
124 set_tmp_attachments_directory
128 set_tmp_attachments_directory
125 with_settings :repositories_encodings => 'UTF-8' do
129 with_settings :repositories_encodings => 'UTF-8' do
126 a = Attachment.new(:container => Issue.find(1),
130 a = Attachment.new(:container => Issue.find(1),
127 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
131 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
128 :author => User.find(1))
132 :author => User.find(1))
129 assert a.save
133 assert a.save
130 assert_equal 'iso8859-1.txt', a.filename
134 assert_equal 'iso8859-1.txt', a.filename
131
135
132 get :show, :id => a.id
136 get :show, :id => a.id
133 assert_response :success
137 assert_response :success
134 assert_template 'file'
138 assert_template 'file'
135 assert_equal 'text/html', @response.content_type
139 assert_equal 'text/html', @response.content_type
136 assert_tag :tag => 'th',
140 assert_tag :tag => 'th',
137 :content => '7',
141 :content => '7',
138 :attributes => { :class => 'line-num' },
142 :attributes => { :class => 'line-num' },
139 :sibling => { :tag => 'td', :content => /Demande cr\?\?e avec succ\?s/ }
143 :sibling => { :tag => 'td', :content => /Demande cr\?\?e avec succ\?s/ }
140 end
144 end
141 end
145 end
142
146
143 def test_show_text_file_latin_1
147 def test_show_text_file_latin_1
144 set_tmp_attachments_directory
148 set_tmp_attachments_directory
145 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
149 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
146 a = Attachment.new(:container => Issue.find(1),
150 a = Attachment.new(:container => Issue.find(1),
147 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
151 :file => uploaded_test_file("iso8859-1.txt", "text/plain"),
148 :author => User.find(1))
152 :author => User.find(1))
149 assert a.save
153 assert a.save
150 assert_equal 'iso8859-1.txt', a.filename
154 assert_equal 'iso8859-1.txt', a.filename
151
155
152 get :show, :id => a.id
156 get :show, :id => a.id
153 assert_response :success
157 assert_response :success
154 assert_template 'file'
158 assert_template 'file'
155 assert_equal 'text/html', @response.content_type
159 assert_equal 'text/html', @response.content_type
156 assert_tag :tag => 'th',
160 assert_tag :tag => 'th',
157 :content => '7',
161 :content => '7',
158 :attributes => { :class => 'line-num' },
162 :attributes => { :class => 'line-num' },
159 :sibling => { :tag => 'td', :content => /Demande créée avec succès/ }
163 :sibling => { :tag => 'td', :content => /Demande créée avec succès/ }
160 end
164 end
161 end
165 end
162
166
163 def test_show_text_file_should_send_if_too_big
167 def test_show_text_file_should_send_if_too_big
164 Setting.file_max_size_displayed = 512
168 Setting.file_max_size_displayed = 512
165 Attachment.find(4).update_attribute :filesize, 754.kilobyte
169 Attachment.find(4).update_attribute :filesize, 754.kilobyte
166
170
167 get :show, :id => 4
171 get :show, :id => 4
168 assert_response :success
172 assert_response :success
169 assert_equal 'application/x-ruby', @response.content_type
173 assert_equal 'application/x-ruby', @response.content_type
170 set_tmp_attachments_directory
174 set_tmp_attachments_directory
171 end
175 end
172
176
173 def test_show_other
177 def test_show_other
174 get :show, :id => 6
178 get :show, :id => 6
175 assert_response :success
179 assert_response :success
176 assert_equal 'application/octet-stream', @response.content_type
180 assert_equal 'application/octet-stream', @response.content_type
177 set_tmp_attachments_directory
181 set_tmp_attachments_directory
178 end
182 end
179
183
180 def test_show_file_from_private_issue_without_permission
184 def test_show_file_from_private_issue_without_permission
181 get :show, :id => 15
185 get :show, :id => 15
182 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
186 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2F15'
183 set_tmp_attachments_directory
187 set_tmp_attachments_directory
184 end
188 end
185
189
186 def test_show_file_from_private_issue_with_permission
190 def test_show_file_from_private_issue_with_permission
187 @request.session[:user_id] = 2
191 @request.session[:user_id] = 2
188 get :show, :id => 15
192 get :show, :id => 15
189 assert_response :success
193 assert_response :success
190 assert_tag 'h2', :content => /private.diff/
194 assert_tag 'h2', :content => /private.diff/
191 set_tmp_attachments_directory
195 set_tmp_attachments_directory
192 end
196 end
193
197
194 def test_download_text_file
198 def test_download_text_file
195 get :download, :id => 4
199 get :download, :id => 4
196 assert_response :success
200 assert_response :success
197 assert_equal 'application/x-ruby', @response.content_type
201 assert_equal 'application/x-ruby', @response.content_type
198 set_tmp_attachments_directory
202 set_tmp_attachments_directory
199 end
203 end
200
204
201 def test_download_version_file_with_issue_tracking_disabled
205 def test_download_version_file_with_issue_tracking_disabled
202 Project.find(1).disable_module! :issue_tracking
206 Project.find(1).disable_module! :issue_tracking
203 get :download, :id => 9
207 get :download, :id => 9
204 assert_response :success
208 assert_response :success
205 end
209 end
206
210
207 def test_download_should_assign_content_type_if_blank
211 def test_download_should_assign_content_type_if_blank
208 Attachment.find(4).update_attribute(:content_type, '')
212 Attachment.find(4).update_attribute(:content_type, '')
209
213
210 get :download, :id => 4
214 get :download, :id => 4
211 assert_response :success
215 assert_response :success
212 assert_equal 'text/x-ruby', @response.content_type
216 assert_equal 'text/x-ruby', @response.content_type
213 set_tmp_attachments_directory
217 set_tmp_attachments_directory
214 end
218 end
215
219
216 def test_download_missing_file
220 def test_download_missing_file
217 get :download, :id => 2
221 get :download, :id => 2
218 assert_response 404
222 assert_response 404
219 set_tmp_attachments_directory
223 set_tmp_attachments_directory
220 end
224 end
221
225
222 def test_anonymous_on_private_private
226 def test_anonymous_on_private_private
223 get :download, :id => 7
227 get :download, :id => 7
224 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
228 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fattachments%2Fdownload%2F7'
225 set_tmp_attachments_directory
229 set_tmp_attachments_directory
226 end
230 end
227
231
228 def test_destroy_issue_attachment
232 def test_destroy_issue_attachment
229 set_tmp_attachments_directory
233 set_tmp_attachments_directory
230 issue = Issue.find(3)
234 issue = Issue.find(3)
231 @request.session[:user_id] = 2
235 @request.session[:user_id] = 2
232
236
233 assert_difference 'issue.attachments.count', -1 do
237 assert_difference 'issue.attachments.count', -1 do
234 delete :destroy, :id => 1
238 delete :destroy, :id => 1
235 end
239 end
236 # no referrer
240 # no referrer
237 assert_redirected_to '/projects/ecookbook'
241 assert_redirected_to '/projects/ecookbook'
238 assert_nil Attachment.find_by_id(1)
242 assert_nil Attachment.find_by_id(1)
239 j = issue.journals.find(:first, :order => 'created_on DESC')
243 j = issue.journals.find(:first, :order => 'created_on DESC')
240 assert_equal 'attachment', j.details.first.property
244 assert_equal 'attachment', j.details.first.property
241 assert_equal '1', j.details.first.prop_key
245 assert_equal '1', j.details.first.prop_key
242 assert_equal 'error281.txt', j.details.first.old_value
246 assert_equal 'error281.txt', j.details.first.old_value
243 end
247 end
244
248
245 def test_destroy_wiki_page_attachment
249 def test_destroy_wiki_page_attachment
246 set_tmp_attachments_directory
250 set_tmp_attachments_directory
247 @request.session[:user_id] = 2
251 @request.session[:user_id] = 2
248 assert_difference 'Attachment.count', -1 do
252 assert_difference 'Attachment.count', -1 do
249 delete :destroy, :id => 3
253 delete :destroy, :id => 3
250 assert_response 302
254 assert_response 302
251 end
255 end
252 end
256 end
253
257
254 def test_destroy_project_attachment
258 def test_destroy_project_attachment
255 set_tmp_attachments_directory
259 set_tmp_attachments_directory
256 @request.session[:user_id] = 2
260 @request.session[:user_id] = 2
257 assert_difference 'Attachment.count', -1 do
261 assert_difference 'Attachment.count', -1 do
258 delete :destroy, :id => 8
262 delete :destroy, :id => 8
259 assert_response 302
263 assert_response 302
260 end
264 end
261 end
265 end
262
266
263 def test_destroy_version_attachment
267 def test_destroy_version_attachment
264 set_tmp_attachments_directory
268 set_tmp_attachments_directory
265 @request.session[:user_id] = 2
269 @request.session[:user_id] = 2
266 assert_difference 'Attachment.count', -1 do
270 assert_difference 'Attachment.count', -1 do
267 delete :destroy, :id => 9
271 delete :destroy, :id => 9
268 assert_response 302
272 assert_response 302
269 end
273 end
270 end
274 end
271
275
272 def test_destroy_without_permission
276 def test_destroy_without_permission
273 set_tmp_attachments_directory
277 set_tmp_attachments_directory
274 assert_no_difference 'Attachment.count' do
278 assert_no_difference 'Attachment.count' do
275 delete :destroy, :id => 3
279 delete :destroy, :id => 3
276 end
280 end
277 assert_response 302
281 assert_response 302
278 assert Attachment.find_by_id(3)
282 assert Attachment.find_by_id(3)
279 end
283 end
280 end
284 end
@@ -1,86 +1,90
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 ApiTest::AttachmentsTest < ActionController::IntegrationTest
20 class ApiTest::AttachmentsTest < ActionController::IntegrationTest
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 :workflows,
28 :workflows,
29 :attachments
29 :attachments
30
30
31 def setup
31 def setup
32 Setting.rest_api_enabled = '1'
32 Setting.rest_api_enabled = '1'
33 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
33 set_fixtures_attachments_directory
34 end
35
36 def teardown
37 set_tmp_attachments_directory
34 end
38 end
35
39
36 context "/attachments/:id" do
40 context "/attachments/:id" do
37 context "GET" do
41 context "GET" do
38 should "return the attachment" do
42 should "return the attachment" do
39 get '/attachments/7.xml', {}, :authorization => credentials('jsmith')
43 get '/attachments/7.xml', {}, :authorization => credentials('jsmith')
40 assert_response :success
44 assert_response :success
41 assert_equal 'application/xml', @response.content_type
45 assert_equal 'application/xml', @response.content_type
42 assert_tag :tag => 'attachment',
46 assert_tag :tag => 'attachment',
43 :child => {
47 :child => {
44 :tag => 'id',
48 :tag => 'id',
45 :content => '7',
49 :content => '7',
46 :sibling => {
50 :sibling => {
47 :tag => 'filename',
51 :tag => 'filename',
48 :content => 'archive.zip',
52 :content => 'archive.zip',
49 :sibling => {
53 :sibling => {
50 :tag => 'content_url',
54 :tag => 'content_url',
51 :content => 'http://www.example.com/attachments/download/7/archive.zip'
55 :content => 'http://www.example.com/attachments/download/7/archive.zip'
52 }
56 }
53 }
57 }
54 }
58 }
55 end
59 end
56
60
57 should "deny access without credentials" do
61 should "deny access without credentials" do
58 get '/attachments/7.xml'
62 get '/attachments/7.xml'
59 assert_response 401
63 assert_response 401
60 set_tmp_attachments_directory
64 set_tmp_attachments_directory
61 end
65 end
62 end
66 end
63 end
67 end
64
68
65 context "/attachments/download/:id/:filename" do
69 context "/attachments/download/:id/:filename" do
66 context "GET" do
70 context "GET" do
67 should "return the attachment content" do
71 should "return the attachment content" do
68 get '/attachments/download/7/archive.zip',
72 get '/attachments/download/7/archive.zip',
69 {}, :authorization => credentials('jsmith')
73 {}, :authorization => credentials('jsmith')
70 assert_response :success
74 assert_response :success
71 assert_equal 'application/octet-stream', @response.content_type
75 assert_equal 'application/octet-stream', @response.content_type
72 set_tmp_attachments_directory
76 set_tmp_attachments_directory
73 end
77 end
74
78
75 should "deny access without credentials" do
79 should "deny access without credentials" do
76 get '/attachments/download/7/archive.zip'
80 get '/attachments/download/7/archive.zip'
77 assert_response 302
81 assert_response 302
78 set_tmp_attachments_directory
82 set_tmp_attachments_directory
79 end
83 end
80 end
84 end
81 end
85 end
82
86
83 def credentials(user, password=nil)
87 def credentials(user, password=nil)
84 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
88 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
85 end
89 end
86 end
90 end
@@ -1,460 +1,464
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 ENV["RAILS_ENV"] = "test"
18 ENV["RAILS_ENV"] = "test"
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require 'test_help'
20 require 'test_help'
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
23
23
24 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
24 require File.expand_path(File.dirname(__FILE__) + '/object_daddy_helpers')
25 include ObjectDaddyHelpers
25 include ObjectDaddyHelpers
26
26
27 class ActiveSupport::TestCase
27 class ActiveSupport::TestCase
28 # Transactional fixtures accelerate your tests by wrapping each test method
28 # Transactional fixtures accelerate your tests by wrapping each test method
29 # in a transaction that's rolled back on completion. This ensures that the
29 # in a transaction that's rolled back on completion. This ensures that the
30 # test database remains unchanged so your fixtures don't have to be reloaded
30 # test database remains unchanged so your fixtures don't have to be reloaded
31 # between every test method. Fewer database queries means faster tests.
31 # between every test method. Fewer database queries means faster tests.
32 #
32 #
33 # Read Mike Clark's excellent walkthrough at
33 # Read Mike Clark's excellent walkthrough at
34 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
34 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
35 #
35 #
36 # Every Active Record database supports transactions except MyISAM tables
36 # Every Active Record database supports transactions except MyISAM tables
37 # in MySQL. Turn off transactional fixtures in this case; however, if you
37 # in MySQL. Turn off transactional fixtures in this case; however, if you
38 # don't care one way or the other, switching from MyISAM to InnoDB tables
38 # don't care one way or the other, switching from MyISAM to InnoDB tables
39 # is recommended.
39 # is recommended.
40 self.use_transactional_fixtures = true
40 self.use_transactional_fixtures = true
41
41
42 # Instantiated fixtures are slow, but give you @david where otherwise you
42 # Instantiated fixtures are slow, but give you @david where otherwise you
43 # would need people(:david). If you don't want to migrate your existing
43 # would need people(:david). If you don't want to migrate your existing
44 # test cases which use the @david style and don't mind the speed hit (each
44 # test cases which use the @david style and don't mind the speed hit (each
45 # instantiated fixtures translates to a database query per test method),
45 # instantiated fixtures translates to a database query per test method),
46 # then set this back to true.
46 # then set this back to true.
47 self.use_instantiated_fixtures = false
47 self.use_instantiated_fixtures = false
48
48
49 # Add more helper methods to be used by all tests here...
49 # Add more helper methods to be used by all tests here...
50
50
51 def log_user(login, password)
51 def log_user(login, password)
52 User.anonymous
52 User.anonymous
53 get "/login"
53 get "/login"
54 assert_equal nil, session[:user_id]
54 assert_equal nil, session[:user_id]
55 assert_response :success
55 assert_response :success
56 assert_template "account/login"
56 assert_template "account/login"
57 post "/login", :username => login, :password => password
57 post "/login", :username => login, :password => password
58 assert_equal login, User.find(session[:user_id]).login
58 assert_equal login, User.find(session[:user_id]).login
59 end
59 end
60
60
61 def uploaded_test_file(name, mime)
61 def uploaded_test_file(name, mime)
62 ActionController::TestUploadedFile.new(
62 ActionController::TestUploadedFile.new(
63 ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime, true)
63 ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime, true)
64 end
64 end
65
65
66 # Mock out a file
66 # Mock out a file
67 def self.mock_file
67 def self.mock_file
68 file = 'a_file.png'
68 file = 'a_file.png'
69 file.stubs(:size).returns(32)
69 file.stubs(:size).returns(32)
70 file.stubs(:original_filename).returns('a_file.png')
70 file.stubs(:original_filename).returns('a_file.png')
71 file.stubs(:content_type).returns('image/png')
71 file.stubs(:content_type).returns('image/png')
72 file.stubs(:read).returns(false)
72 file.stubs(:read).returns(false)
73 file
73 file
74 end
74 end
75
75
76 def mock_file
76 def mock_file
77 self.class.mock_file
77 self.class.mock_file
78 end
78 end
79
79
80 def mock_file_with_options(options={})
80 def mock_file_with_options(options={})
81 file = ''
81 file = ''
82 file.stubs(:size).returns(32)
82 file.stubs(:size).returns(32)
83 original_filename = options[:original_filename] || nil
83 original_filename = options[:original_filename] || nil
84 file.stubs(:original_filename).returns(original_filename)
84 file.stubs(:original_filename).returns(original_filename)
85 content_type = options[:content_type] || nil
85 content_type = options[:content_type] || nil
86 file.stubs(:content_type).returns(content_type)
86 file.stubs(:content_type).returns(content_type)
87 file.stubs(:read).returns(false)
87 file.stubs(:read).returns(false)
88 file
88 file
89 end
89 end
90
90
91 # Use a temporary directory for attachment related tests
91 # Use a temporary directory for attachment related tests
92 def set_tmp_attachments_directory
92 def set_tmp_attachments_directory
93 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
93 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
94 unless File.directory?("#{Rails.root}/tmp/test/attachments")
94 unless File.directory?("#{Rails.root}/tmp/test/attachments")
95 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
95 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
96 end
96 end
97 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
97 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
98 end
98 end
99
99
100 def set_fixtures_attachments_directory
101 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
102 end
103
100 def with_settings(options, &block)
104 def with_settings(options, &block)
101 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h}
105 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h}
102 options.each {|k, v| Setting[k] = v}
106 options.each {|k, v| Setting[k] = v}
103 yield
107 yield
104 ensure
108 ensure
105 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
109 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
106 end
110 end
107
111
108 def change_user_password(login, new_password)
112 def change_user_password(login, new_password)
109 user = User.first(:conditions => {:login => login})
113 user = User.first(:conditions => {:login => login})
110 user.password, user.password_confirmation = new_password, new_password
114 user.password, user.password_confirmation = new_password, new_password
111 user.save!
115 user.save!
112 end
116 end
113
117
114 def self.ldap_configured?
118 def self.ldap_configured?
115 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
119 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
116 return @test_ldap.bind
120 return @test_ldap.bind
117 rescue Exception => e
121 rescue Exception => e
118 # LDAP is not listening
122 # LDAP is not listening
119 return nil
123 return nil
120 end
124 end
121
125
122 # Returns the path to the test +vendor+ repository
126 # Returns the path to the test +vendor+ repository
123 def self.repository_path(vendor)
127 def self.repository_path(vendor)
124 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
128 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
125 end
129 end
126
130
127 # Returns the url of the subversion test repository
131 # Returns the url of the subversion test repository
128 def self.subversion_repository_url
132 def self.subversion_repository_url
129 path = repository_path('subversion')
133 path = repository_path('subversion')
130 path = '/' + path unless path.starts_with?('/')
134 path = '/' + path unless path.starts_with?('/')
131 "file://#{path}"
135 "file://#{path}"
132 end
136 end
133
137
134 # Returns true if the +vendor+ test repository is configured
138 # Returns true if the +vendor+ test repository is configured
135 def self.repository_configured?(vendor)
139 def self.repository_configured?(vendor)
136 File.directory?(repository_path(vendor))
140 File.directory?(repository_path(vendor))
137 end
141 end
138
142
139 def assert_error_tag(options={})
143 def assert_error_tag(options={})
140 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
144 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
141 end
145 end
142
146
143 def assert_include(expected, s)
147 def assert_include(expected, s)
144 assert s.include?(expected), "\"#{expected}\" not found in \"#{s}\""
148 assert s.include?(expected), "\"#{expected}\" not found in \"#{s}\""
145 end
149 end
146
150
147 # Shoulda macros
151 # Shoulda macros
148 def self.should_render_404
152 def self.should_render_404
149 should_respond_with :not_found
153 should_respond_with :not_found
150 should_render_template 'common/error'
154 should_render_template 'common/error'
151 end
155 end
152
156
153 def self.should_have_before_filter(expected_method, options = {})
157 def self.should_have_before_filter(expected_method, options = {})
154 should_have_filter('before', expected_method, options)
158 should_have_filter('before', expected_method, options)
155 end
159 end
156
160
157 def self.should_have_after_filter(expected_method, options = {})
161 def self.should_have_after_filter(expected_method, options = {})
158 should_have_filter('after', expected_method, options)
162 should_have_filter('after', expected_method, options)
159 end
163 end
160
164
161 def self.should_have_filter(filter_type, expected_method, options)
165 def self.should_have_filter(filter_type, expected_method, options)
162 description = "have #{filter_type}_filter :#{expected_method}"
166 description = "have #{filter_type}_filter :#{expected_method}"
163 description << " with #{options.inspect}" unless options.empty?
167 description << " with #{options.inspect}" unless options.empty?
164
168
165 should description do
169 should description do
166 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
170 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
167 expected = klass.new(:filter, expected_method.to_sym, options)
171 expected = klass.new(:filter, expected_method.to_sym, options)
168 assert_equal 1, @controller.class.filter_chain.select { |filter|
172 assert_equal 1, @controller.class.filter_chain.select { |filter|
169 filter.method == expected.method && filter.kind == expected.kind &&
173 filter.method == expected.method && filter.kind == expected.kind &&
170 filter.options == expected.options && filter.class == expected.class
174 filter.options == expected.options && filter.class == expected.class
171 }.size
175 }.size
172 end
176 end
173 end
177 end
174
178
175 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
179 def self.should_show_the_old_and_new_values_for(prop_key, model, &block)
176 context "" do
180 context "" do
177 setup do
181 setup do
178 if block_given?
182 if block_given?
179 instance_eval &block
183 instance_eval &block
180 else
184 else
181 @old_value = model.generate!
185 @old_value = model.generate!
182 @new_value = model.generate!
186 @new_value = model.generate!
183 end
187 end
184 end
188 end
185
189
186 should "use the new value's name" do
190 should "use the new value's name" do
187 @detail = JournalDetail.generate!(:property => 'attr',
191 @detail = JournalDetail.generate!(:property => 'attr',
188 :old_value => @old_value.id,
192 :old_value => @old_value.id,
189 :value => @new_value.id,
193 :value => @new_value.id,
190 :prop_key => prop_key)
194 :prop_key => prop_key)
191
195
192 assert_match @new_value.name, show_detail(@detail, true)
196 assert_match @new_value.name, show_detail(@detail, true)
193 end
197 end
194
198
195 should "use the old value's name" do
199 should "use the old value's name" do
196 @detail = JournalDetail.generate!(:property => 'attr',
200 @detail = JournalDetail.generate!(:property => 'attr',
197 :old_value => @old_value.id,
201 :old_value => @old_value.id,
198 :value => @new_value.id,
202 :value => @new_value.id,
199 :prop_key => prop_key)
203 :prop_key => prop_key)
200
204
201 assert_match @old_value.name, show_detail(@detail, true)
205 assert_match @old_value.name, show_detail(@detail, true)
202 end
206 end
203 end
207 end
204 end
208 end
205
209
206 def self.should_create_a_new_user(&block)
210 def self.should_create_a_new_user(&block)
207 should "create a new user" do
211 should "create a new user" do
208 user = instance_eval &block
212 user = instance_eval &block
209 assert user
213 assert user
210 assert_kind_of User, user
214 assert_kind_of User, user
211 assert !user.new_record?
215 assert !user.new_record?
212 end
216 end
213 end
217 end
214
218
215 # Test that a request allows the three types of API authentication
219 # Test that a request allows the three types of API authentication
216 #
220 #
217 # * HTTP Basic with username and password
221 # * HTTP Basic with username and password
218 # * HTTP Basic with an api key for the username
222 # * HTTP Basic with an api key for the username
219 # * Key based with the key=X parameter
223 # * Key based with the key=X parameter
220 #
224 #
221 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
225 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
222 # @param [String] url the request url
226 # @param [String] url the request url
223 # @param [optional, Hash] parameters additional request parameters
227 # @param [optional, Hash] parameters additional request parameters
224 # @param [optional, Hash] options additional options
228 # @param [optional, Hash] options additional options
225 # @option options [Symbol] :success_code Successful response code (:success)
229 # @option options [Symbol] :success_code Successful response code (:success)
226 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
230 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
227 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
231 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
228 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
232 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
229 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
233 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
230 should_allow_key_based_auth(http_method, url, parameters, options)
234 should_allow_key_based_auth(http_method, url, parameters, options)
231 end
235 end
232
236
233 # Test that a request allows the username and password for HTTP BASIC
237 # Test that a request allows the username and password for HTTP BASIC
234 #
238 #
235 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
239 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
236 # @param [String] url the request url
240 # @param [String] url the request url
237 # @param [optional, Hash] parameters additional request parameters
241 # @param [optional, Hash] parameters additional request parameters
238 # @param [optional, Hash] options additional options
242 # @param [optional, Hash] options additional options
239 # @option options [Symbol] :success_code Successful response code (:success)
243 # @option options [Symbol] :success_code Successful response code (:success)
240 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
244 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
241 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
245 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
242 success_code = options[:success_code] || :success
246 success_code = options[:success_code] || :success
243 failure_code = options[:failure_code] || :unauthorized
247 failure_code = options[:failure_code] || :unauthorized
244
248
245 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
249 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
246 context "with a valid HTTP authentication" do
250 context "with a valid HTTP authentication" do
247 setup do
251 setup do
248 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
252 @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
249 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
253 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
250 send(http_method, url, parameters, {:authorization => @authorization})
254 send(http_method, url, parameters, {:authorization => @authorization})
251 end
255 end
252
256
253 should_respond_with success_code
257 should_respond_with success_code
254 should_respond_with_content_type_based_on_url(url)
258 should_respond_with_content_type_based_on_url(url)
255 should "login as the user" do
259 should "login as the user" do
256 assert_equal @user, User.current
260 assert_equal @user, User.current
257 end
261 end
258 end
262 end
259
263
260 context "with an invalid HTTP authentication" do
264 context "with an invalid HTTP authentication" do
261 setup do
265 setup do
262 @user = User.generate_with_protected!
266 @user = User.generate_with_protected!
263 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
267 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
264 send(http_method, url, parameters, {:authorization => @authorization})
268 send(http_method, url, parameters, {:authorization => @authorization})
265 end
269 end
266
270
267 should_respond_with failure_code
271 should_respond_with failure_code
268 should_respond_with_content_type_based_on_url(url)
272 should_respond_with_content_type_based_on_url(url)
269 should "not login as the user" do
273 should "not login as the user" do
270 assert_equal User.anonymous, User.current
274 assert_equal User.anonymous, User.current
271 end
275 end
272 end
276 end
273
277
274 context "without credentials" do
278 context "without credentials" do
275 setup do
279 setup do
276 send(http_method, url, parameters, {:authorization => ''})
280 send(http_method, url, parameters, {:authorization => ''})
277 end
281 end
278
282
279 should_respond_with failure_code
283 should_respond_with failure_code
280 should_respond_with_content_type_based_on_url(url)
284 should_respond_with_content_type_based_on_url(url)
281 should "include_www_authenticate_header" do
285 should "include_www_authenticate_header" do
282 assert @controller.response.headers.has_key?('WWW-Authenticate')
286 assert @controller.response.headers.has_key?('WWW-Authenticate')
283 end
287 end
284 end
288 end
285 end
289 end
286
290
287 end
291 end
288
292
289 # Test that a request allows the API key with HTTP BASIC
293 # Test that a request allows the API key with HTTP BASIC
290 #
294 #
291 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
295 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
292 # @param [String] url the request url
296 # @param [String] url the request url
293 # @param [optional, Hash] parameters additional request parameters
297 # @param [optional, Hash] parameters additional request parameters
294 # @param [optional, Hash] options additional options
298 # @param [optional, Hash] options additional options
295 # @option options [Symbol] :success_code Successful response code (:success)
299 # @option options [Symbol] :success_code Successful response code (:success)
296 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
300 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
297 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
301 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
298 success_code = options[:success_code] || :success
302 success_code = options[:success_code] || :success
299 failure_code = options[:failure_code] || :unauthorized
303 failure_code = options[:failure_code] || :unauthorized
300
304
301 context "should allow http basic auth with a key for #{http_method} #{url}" do
305 context "should allow http basic auth with a key for #{http_method} #{url}" do
302 context "with a valid HTTP authentication using the API token" do
306 context "with a valid HTTP authentication using the API token" do
303 setup do
307 setup do
304 @user = User.generate_with_protected!(:admin => true)
308 @user = User.generate_with_protected!(:admin => true)
305 @token = Token.generate!(:user => @user, :action => 'api')
309 @token = Token.generate!(:user => @user, :action => 'api')
306 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
310 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
307 send(http_method, url, parameters, {:authorization => @authorization})
311 send(http_method, url, parameters, {:authorization => @authorization})
308 end
312 end
309
313
310 should_respond_with success_code
314 should_respond_with success_code
311 should_respond_with_content_type_based_on_url(url)
315 should_respond_with_content_type_based_on_url(url)
312 should_be_a_valid_response_string_based_on_url(url)
316 should_be_a_valid_response_string_based_on_url(url)
313 should "login as the user" do
317 should "login as the user" do
314 assert_equal @user, User.current
318 assert_equal @user, User.current
315 end
319 end
316 end
320 end
317
321
318 context "with an invalid HTTP authentication" do
322 context "with an invalid HTTP authentication" do
319 setup do
323 setup do
320 @user = User.generate_with_protected!
324 @user = User.generate_with_protected!
321 @token = Token.generate!(:user => @user, :action => 'feeds')
325 @token = Token.generate!(:user => @user, :action => 'feeds')
322 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
326 @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
323 send(http_method, url, parameters, {:authorization => @authorization})
327 send(http_method, url, parameters, {:authorization => @authorization})
324 end
328 end
325
329
326 should_respond_with failure_code
330 should_respond_with failure_code
327 should_respond_with_content_type_based_on_url(url)
331 should_respond_with_content_type_based_on_url(url)
328 should "not login as the user" do
332 should "not login as the user" do
329 assert_equal User.anonymous, User.current
333 assert_equal User.anonymous, User.current
330 end
334 end
331 end
335 end
332 end
336 end
333 end
337 end
334
338
335 # Test that a request allows full key authentication
339 # Test that a request allows full key authentication
336 #
340 #
337 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
341 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
338 # @param [String] url the request url, without the key=ZXY parameter
342 # @param [String] url the request url, without the key=ZXY parameter
339 # @param [optional, Hash] parameters additional request parameters
343 # @param [optional, Hash] parameters additional request parameters
340 # @param [optional, Hash] options additional options
344 # @param [optional, Hash] options additional options
341 # @option options [Symbol] :success_code Successful response code (:success)
345 # @option options [Symbol] :success_code Successful response code (:success)
342 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
346 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
343 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
347 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
344 success_code = options[:success_code] || :success
348 success_code = options[:success_code] || :success
345 failure_code = options[:failure_code] || :unauthorized
349 failure_code = options[:failure_code] || :unauthorized
346
350
347 context "should allow key based auth using key=X for #{http_method} #{url}" do
351 context "should allow key based auth using key=X for #{http_method} #{url}" do
348 context "with a valid api token" do
352 context "with a valid api token" do
349 setup do
353 setup do
350 @user = User.generate_with_protected!(:admin => true)
354 @user = User.generate_with_protected!(:admin => true)
351 @token = Token.generate!(:user => @user, :action => 'api')
355 @token = Token.generate!(:user => @user, :action => 'api')
352 # Simple url parse to add on ?key= or &key=
356 # Simple url parse to add on ?key= or &key=
353 request_url = if url.match(/\?/)
357 request_url = if url.match(/\?/)
354 url + "&key=#{@token.value}"
358 url + "&key=#{@token.value}"
355 else
359 else
356 url + "?key=#{@token.value}"
360 url + "?key=#{@token.value}"
357 end
361 end
358 send(http_method, request_url, parameters)
362 send(http_method, request_url, parameters)
359 end
363 end
360
364
361 should_respond_with success_code
365 should_respond_with success_code
362 should_respond_with_content_type_based_on_url(url)
366 should_respond_with_content_type_based_on_url(url)
363 should_be_a_valid_response_string_based_on_url(url)
367 should_be_a_valid_response_string_based_on_url(url)
364 should "login as the user" do
368 should "login as the user" do
365 assert_equal @user, User.current
369 assert_equal @user, User.current
366 end
370 end
367 end
371 end
368
372
369 context "with an invalid api token" do
373 context "with an invalid api token" do
370 setup do
374 setup do
371 @user = User.generate_with_protected!
375 @user = User.generate_with_protected!
372 @token = Token.generate!(:user => @user, :action => 'feeds')
376 @token = Token.generate!(:user => @user, :action => 'feeds')
373 # Simple url parse to add on ?key= or &key=
377 # Simple url parse to add on ?key= or &key=
374 request_url = if url.match(/\?/)
378 request_url = if url.match(/\?/)
375 url + "&key=#{@token.value}"
379 url + "&key=#{@token.value}"
376 else
380 else
377 url + "?key=#{@token.value}"
381 url + "?key=#{@token.value}"
378 end
382 end
379 send(http_method, request_url, parameters)
383 send(http_method, request_url, parameters)
380 end
384 end
381
385
382 should_respond_with failure_code
386 should_respond_with failure_code
383 should_respond_with_content_type_based_on_url(url)
387 should_respond_with_content_type_based_on_url(url)
384 should "not login as the user" do
388 should "not login as the user" do
385 assert_equal User.anonymous, User.current
389 assert_equal User.anonymous, User.current
386 end
390 end
387 end
391 end
388 end
392 end
389
393
390 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
394 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
391 setup do
395 setup do
392 @user = User.generate_with_protected!(:admin => true)
396 @user = User.generate_with_protected!(:admin => true)
393 @token = Token.generate!(:user => @user, :action => 'api')
397 @token = Token.generate!(:user => @user, :action => 'api')
394 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
398 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
395 end
399 end
396
400
397 should_respond_with success_code
401 should_respond_with success_code
398 should_respond_with_content_type_based_on_url(url)
402 should_respond_with_content_type_based_on_url(url)
399 should_be_a_valid_response_string_based_on_url(url)
403 should_be_a_valid_response_string_based_on_url(url)
400 should "login as the user" do
404 should "login as the user" do
401 assert_equal @user, User.current
405 assert_equal @user, User.current
402 end
406 end
403 end
407 end
404 end
408 end
405
409
406 # Uses should_respond_with_content_type based on what's in the url:
410 # Uses should_respond_with_content_type based on what's in the url:
407 #
411 #
408 # '/project/issues.xml' => should_respond_with_content_type :xml
412 # '/project/issues.xml' => should_respond_with_content_type :xml
409 # '/project/issues.json' => should_respond_with_content_type :json
413 # '/project/issues.json' => should_respond_with_content_type :json
410 #
414 #
411 # @param [String] url Request
415 # @param [String] url Request
412 def self.should_respond_with_content_type_based_on_url(url)
416 def self.should_respond_with_content_type_based_on_url(url)
413 case
417 case
414 when url.match(/xml/i)
418 when url.match(/xml/i)
415 should_respond_with_content_type :xml
419 should_respond_with_content_type :xml
416 when url.match(/json/i)
420 when url.match(/json/i)
417 should_respond_with_content_type :json
421 should_respond_with_content_type :json
418 else
422 else
419 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
423 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
420 end
424 end
421
425
422 end
426 end
423
427
424 # Uses the url to assert which format the response should be in
428 # Uses the url to assert which format the response should be in
425 #
429 #
426 # '/project/issues.xml' => should_be_a_valid_xml_string
430 # '/project/issues.xml' => should_be_a_valid_xml_string
427 # '/project/issues.json' => should_be_a_valid_json_string
431 # '/project/issues.json' => should_be_a_valid_json_string
428 #
432 #
429 # @param [String] url Request
433 # @param [String] url Request
430 def self.should_be_a_valid_response_string_based_on_url(url)
434 def self.should_be_a_valid_response_string_based_on_url(url)
431 case
435 case
432 when url.match(/xml/i)
436 when url.match(/xml/i)
433 should_be_a_valid_xml_string
437 should_be_a_valid_xml_string
434 when url.match(/json/i)
438 when url.match(/json/i)
435 should_be_a_valid_json_string
439 should_be_a_valid_json_string
436 else
440 else
437 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
441 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
438 end
442 end
439
443
440 end
444 end
441
445
442 # Checks that the response is a valid JSON string
446 # Checks that the response is a valid JSON string
443 def self.should_be_a_valid_json_string
447 def self.should_be_a_valid_json_string
444 should "be a valid JSON string (or empty)" do
448 should "be a valid JSON string (or empty)" do
445 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
449 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
446 end
450 end
447 end
451 end
448
452
449 # Checks that the response is a valid XML string
453 # Checks that the response is a valid XML string
450 def self.should_be_a_valid_xml_string
454 def self.should_be_a_valid_xml_string
451 should "be a valid XML string" do
455 should "be a valid XML string" do
452 assert REXML::Document.new(response.body)
456 assert REXML::Document.new(response.body)
453 end
457 end
454 end
458 end
455
459
456 end
460 end
457
461
458 # Simple module to "namespace" all of the API tests
462 # Simple module to "namespace" all of the API tests
459 module ApiTest
463 module ApiTest
460 end
464 end
@@ -1,168 +1,168
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
21
22 class AttachmentTest < ActiveSupport::TestCase
22 class AttachmentTest < ActiveSupport::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
25
26 class MockFile
26 class MockFile
27 attr_reader :original_filename, :content_type, :content, :size
27 attr_reader :original_filename, :content_type, :content, :size
28
28
29 def initialize(attributes)
29 def initialize(attributes)
30 @original_filename = attributes[:original_filename]
30 @original_filename = attributes[:original_filename]
31 @content_type = attributes[:content_type]
31 @content_type = attributes[:content_type]
32 @content = attributes[:content] || "Content"
32 @content = attributes[:content] || "Content"
33 @size = content.size
33 @size = content.size
34 end
34 end
35 end
35 end
36
36
37 def setup
37 def setup
38 set_tmp_attachments_directory
38 set_tmp_attachments_directory
39 end
39 end
40
40
41 def test_create
41 def test_create
42 a = Attachment.new(:container => Issue.find(1),
42 a = Attachment.new(:container => Issue.find(1),
43 :file => uploaded_test_file("testfile.txt", "text/plain"),
43 :file => uploaded_test_file("testfile.txt", "text/plain"),
44 :author => User.find(1))
44 :author => User.find(1))
45 assert a.save
45 assert a.save
46 assert_equal 'testfile.txt', a.filename
46 assert_equal 'testfile.txt', a.filename
47 assert_equal 59, a.filesize
47 assert_equal 59, a.filesize
48 assert_equal 'text/plain', a.content_type
48 assert_equal 'text/plain', a.content_type
49 assert_equal 0, a.downloads
49 assert_equal 0, a.downloads
50 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
50 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
51 assert File.exist?(a.diskfile)
51 assert File.exist?(a.diskfile)
52 assert_equal 59, File.size(a.diskfile)
52 assert_equal 59, File.size(a.diskfile)
53 end
53 end
54
54
55 def test_destroy
55 def test_destroy
56 a = Attachment.new(:container => Issue.find(1),
56 a = Attachment.new(:container => Issue.find(1),
57 :file => uploaded_test_file("testfile.txt", "text/plain"),
57 :file => uploaded_test_file("testfile.txt", "text/plain"),
58 :author => User.find(1))
58 :author => User.find(1))
59 assert a.save
59 assert a.save
60 assert_equal 'testfile.txt', a.filename
60 assert_equal 'testfile.txt', a.filename
61 assert_equal 59, a.filesize
61 assert_equal 59, a.filesize
62 assert_equal 'text/plain', a.content_type
62 assert_equal 'text/plain', a.content_type
63 assert_equal 0, a.downloads
63 assert_equal 0, a.downloads
64 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
64 assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest
65 diskfile = a.diskfile
65 diskfile = a.diskfile
66 assert File.exist?(diskfile)
66 assert File.exist?(diskfile)
67 assert_equal 59, File.size(a.diskfile)
67 assert_equal 59, File.size(a.diskfile)
68 assert a.destroy
68 assert a.destroy
69 assert !File.exist?(diskfile)
69 assert !File.exist?(diskfile)
70 end
70 end
71
71
72 def test_create_should_auto_assign_content_type
72 def test_create_should_auto_assign_content_type
73 a = Attachment.new(:container => Issue.find(1),
73 a = Attachment.new(:container => Issue.find(1),
74 :file => uploaded_test_file("testfile.txt", ""),
74 :file => uploaded_test_file("testfile.txt", ""),
75 :author => User.find(1))
75 :author => User.find(1))
76 assert a.save
76 assert a.save
77 assert_equal 'text/plain', a.content_type
77 assert_equal 'text/plain', a.content_type
78 end
78 end
79
79
80 def test_identical_attachments_at_the_same_time_should_not_overwrite
80 def test_identical_attachments_at_the_same_time_should_not_overwrite
81 a1 = Attachment.create!(:container => Issue.find(1),
81 a1 = Attachment.create!(:container => Issue.find(1),
82 :file => uploaded_test_file("testfile.txt", ""),
82 :file => uploaded_test_file("testfile.txt", ""),
83 :author => User.find(1))
83 :author => User.find(1))
84 a2 = Attachment.create!(:container => Issue.find(1),
84 a2 = Attachment.create!(:container => Issue.find(1),
85 :file => uploaded_test_file("testfile.txt", ""),
85 :file => uploaded_test_file("testfile.txt", ""),
86 :author => User.find(1))
86 :author => User.find(1))
87 assert a1.disk_filename != a2.disk_filename
87 assert a1.disk_filename != a2.disk_filename
88 end
88 end
89
89
90 def test_filename_should_be_basenamed
90 def test_filename_should_be_basenamed
91 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
91 a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file"))
92 assert_equal 'file', a.filename
92 assert_equal 'file', a.filename
93 end
93 end
94
94
95 def test_filename_should_be_sanitized
95 def test_filename_should_be_sanitized
96 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
96 a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars"))
97 assert_equal 'valid_[] invalid_chars', a.filename
97 assert_equal 'valid_[] invalid_chars', a.filename
98 end
98 end
99
99
100 def test_diskfilename
100 def test_diskfilename
101 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
101 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
102 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
102 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
103 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
103 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
104 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
104 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
105 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
105 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
106 end
106 end
107
107
108 context "Attachmnet.attach_files" do
108 context "Attachmnet.attach_files" do
109 should "attach the file" do
109 should "attach the file" do
110 issue = Issue.first
110 issue = Issue.first
111 assert_difference 'Attachment.count' do
111 assert_difference 'Attachment.count' do
112 Attachment.attach_files(issue,
112 Attachment.attach_files(issue,
113 '1' => {
113 '1' => {
114 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
114 'file' => uploaded_test_file('testfile.txt', 'text/plain'),
115 'description' => 'test'
115 'description' => 'test'
116 })
116 })
117 end
117 end
118
118
119 attachment = Attachment.first(:order => 'id DESC')
119 attachment = Attachment.first(:order => 'id DESC')
120 assert_equal issue, attachment.container
120 assert_equal issue, attachment.container
121 assert_equal 'testfile.txt', attachment.filename
121 assert_equal 'testfile.txt', attachment.filename
122 assert_equal 59, attachment.filesize
122 assert_equal 59, attachment.filesize
123 assert_equal 'test', attachment.description
123 assert_equal 'test', attachment.description
124 assert_equal 'text/plain', attachment.content_type
124 assert_equal 'text/plain', attachment.content_type
125 assert File.exists?(attachment.diskfile)
125 assert File.exists?(attachment.diskfile)
126 assert_equal 59, File.size(attachment.diskfile)
126 assert_equal 59, File.size(attachment.diskfile)
127 end
127 end
128
128
129 should "add unsaved files to the object as unsaved attachments" do
129 should "add unsaved files to the object as unsaved attachments" do
130 # Max size of 0 to force Attachment creation failures
130 # Max size of 0 to force Attachment creation failures
131 with_settings(:attachment_max_size => 0) do
131 with_settings(:attachment_max_size => 0) do
132 @project = Project.generate!
132 @project = Project.generate!
133 response = Attachment.attach_files(@project, {
133 response = Attachment.attach_files(@project, {
134 '1' => {'file' => mock_file, 'description' => 'test'},
134 '1' => {'file' => mock_file, 'description' => 'test'},
135 '2' => {'file' => mock_file, 'description' => 'test'}
135 '2' => {'file' => mock_file, 'description' => 'test'}
136 })
136 })
137
137
138 assert response[:unsaved].present?
138 assert response[:unsaved].present?
139 assert_equal 2, response[:unsaved].length
139 assert_equal 2, response[:unsaved].length
140 assert response[:unsaved].first.new_record?
140 assert response[:unsaved].first.new_record?
141 assert response[:unsaved].second.new_record?
141 assert response[:unsaved].second.new_record?
142 assert_equal response[:unsaved], @project.unsaved_attachments
142 assert_equal response[:unsaved], @project.unsaved_attachments
143 end
143 end
144 end
144 end
145 end
145 end
146
146
147 def test_latest_attach
147 def test_latest_attach
148 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
148 set_fixtures_attachments_directory
149 a1 = Attachment.find(16)
149 a1 = Attachment.find(16)
150 assert_equal "testfile.png", a1.filename
150 assert_equal "testfile.png", a1.filename
151 assert a1.readable?
151 assert a1.readable?
152 assert (! a1.visible?(User.anonymous))
152 assert (! a1.visible?(User.anonymous))
153 assert a1.visible?(User.find(2))
153 assert a1.visible?(User.find(2))
154 a2 = Attachment.find(17)
154 a2 = Attachment.find(17)
155 assert_equal "testfile.PNG", a2.filename
155 assert_equal "testfile.PNG", a2.filename
156 assert a2.readable?
156 assert a2.readable?
157 assert (! a2.visible?(User.anonymous))
157 assert (! a2.visible?(User.anonymous))
158 assert a2.visible?(User.find(2))
158 assert a2.visible?(User.find(2))
159 assert a1.created_on < a2.created_on
159 assert a1.created_on < a2.created_on
160
160
161 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
161 la1 = Attachment.latest_attach([a1, a2], "testfile.png")
162 assert_equal 17, la1.id
162 assert_equal 17, la1.id
163 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
163 la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG")
164 assert_equal 17, la2.id
164 assert_equal 17, la2.id
165
165
166 set_tmp_attachments_directory
166 set_tmp_attachments_directory
167 end
167 end
168 end
168 end
@@ -1,878 +1,878
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 ApplicationHelperTest < ActionView::TestCase
20 class ApplicationHelperTest < ActionView::TestCase
21 fixtures :projects, :roles, :enabled_modules, :users,
21 fixtures :projects, :roles, :enabled_modules, :users,
22 :repositories, :changesets,
22 :repositories, :changesets,
23 :trackers, :issue_statuses, :issues, :versions, :documents,
23 :trackers, :issue_statuses, :issues, :versions, :documents,
24 :wikis, :wiki_pages, :wiki_contents,
24 :wikis, :wiki_pages, :wiki_contents,
25 :boards, :messages, :news,
25 :boards, :messages, :news,
26 :attachments, :enumerations
26 :attachments, :enumerations
27
27
28 def setup
28 def setup
29 super
29 super
30 set_tmp_attachments_directory
30 set_tmp_attachments_directory
31 end
31 end
32
32
33 context "#link_to_if_authorized" do
33 context "#link_to_if_authorized" do
34 context "authorized user" do
34 context "authorized user" do
35 should "be tested"
35 should "be tested"
36 end
36 end
37
37
38 context "unauthorized user" do
38 context "unauthorized user" do
39 should "be tested"
39 should "be tested"
40 end
40 end
41
41
42 should "allow using the :controller and :action for the target link" do
42 should "allow using the :controller and :action for the target link" do
43 User.current = User.find_by_login('admin')
43 User.current = User.find_by_login('admin')
44
44
45 @project = Issue.first.project # Used by helper
45 @project = Issue.first.project # Used by helper
46 response = link_to_if_authorized("By controller/action",
46 response = link_to_if_authorized("By controller/action",
47 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
47 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
48 assert_match /href/, response
48 assert_match /href/, response
49 end
49 end
50
50
51 end
51 end
52
52
53 def test_auto_links
53 def test_auto_links
54 to_test = {
54 to_test = {
55 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
55 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
56 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
56 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
57 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
57 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
58 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
58 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
59 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
60 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
60 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
61 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
61 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
62 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
62 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
63 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
63 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
64 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
64 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
65 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
65 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
66 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
66 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
67 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
67 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
68 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
68 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
69 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
69 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
70 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
70 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
71 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
71 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
72 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
72 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
73 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
73 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
74 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
74 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
75 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
75 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
76 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
76 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
77 # two exclamation marks
77 # two exclamation marks
78 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
78 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
79 # escaping
79 # escaping
80 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
80 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
81 # wrap in angle brackets
81 # wrap in angle brackets
82 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
82 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
83 }
83 }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 end
85 end
86
86
87 def test_auto_mailto
87 def test_auto_mailto
88 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
88 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
89 textilizable('test@foo.bar')
89 textilizable('test@foo.bar')
90 end
90 end
91
91
92 def test_inline_images
92 def test_inline_images
93 to_test = {
93 to_test = {
94 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
94 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
95 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
95 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
96 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
96 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
97 # inline styles should be stripped
97 # inline styles should be stripped
98 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
98 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
99 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
99 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
100 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
100 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
101 }
101 }
102 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
102 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
103 end
103 end
104
104
105 def test_inline_images_inside_tags
105 def test_inline_images_inside_tags
106 raw = <<-RAW
106 raw = <<-RAW
107 h1. !foo.png! Heading
107 h1. !foo.png! Heading
108
108
109 Centered image:
109 Centered image:
110
110
111 p=. !bar.gif!
111 p=. !bar.gif!
112 RAW
112 RAW
113
113
114 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
114 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
115 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
115 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
116 end
116 end
117
117
118 def test_attached_images
118 def test_attached_images
119 to_test = {
119 to_test = {
120 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
120 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
121 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
121 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
122 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
123 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
123 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
124 # link image
124 # link image
125 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
125 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
126 }
126 }
127 attachments = Attachment.find(:all)
127 attachments = Attachment.find(:all)
128 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
128 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
129 end
129 end
130
130
131 def test_attached_images_filename_extension
131 def test_attached_images_filename_extension
132 set_tmp_attachments_directory
132 set_tmp_attachments_directory
133 a1 = Attachment.new(
133 a1 = Attachment.new(
134 :container => Issue.find(1),
134 :container => Issue.find(1),
135 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
135 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
136 :author => User.find(1))
136 :author => User.find(1))
137 assert a1.save
137 assert a1.save
138 assert_equal "testtest.JPG", a1.filename
138 assert_equal "testtest.JPG", a1.filename
139 assert_equal "image/jpeg", a1.content_type
139 assert_equal "image/jpeg", a1.content_type
140 assert a1.image?
140 assert a1.image?
141
141
142 a2 = Attachment.new(
142 a2 = Attachment.new(
143 :container => Issue.find(1),
143 :container => Issue.find(1),
144 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
144 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
145 :author => User.find(1))
145 :author => User.find(1))
146 assert a2.save
146 assert a2.save
147 assert_equal "testtest.jpeg", a2.filename
147 assert_equal "testtest.jpeg", a2.filename
148 assert_equal "image/jpeg", a2.content_type
148 assert_equal "image/jpeg", a2.content_type
149 assert a2.image?
149 assert a2.image?
150
150
151 a3 = Attachment.new(
151 a3 = Attachment.new(
152 :container => Issue.find(1),
152 :container => Issue.find(1),
153 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
153 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
154 :author => User.find(1))
154 :author => User.find(1))
155 assert a3.save
155 assert a3.save
156 assert_equal "testtest.JPE", a3.filename
156 assert_equal "testtest.JPE", a3.filename
157 assert_equal "image/jpeg", a3.content_type
157 assert_equal "image/jpeg", a3.content_type
158 assert a3.image?
158 assert a3.image?
159
159
160 a4 = Attachment.new(
160 a4 = Attachment.new(
161 :container => Issue.find(1),
161 :container => Issue.find(1),
162 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
162 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
163 :author => User.find(1))
163 :author => User.find(1))
164 assert a4.save
164 assert a4.save
165 assert_equal "Testtest.BMP", a4.filename
165 assert_equal "Testtest.BMP", a4.filename
166 assert_equal "image/x-ms-bmp", a4.content_type
166 assert_equal "image/x-ms-bmp", a4.content_type
167 assert a4.image?
167 assert a4.image?
168
168
169 to_test = {
169 to_test = {
170 'Inline image: !testtest.jpg!' =>
170 'Inline image: !testtest.jpg!' =>
171 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
171 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
172 'Inline image: !testtest.jpeg!' =>
172 'Inline image: !testtest.jpeg!' =>
173 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
173 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
174 'Inline image: !testtest.jpe!' =>
174 'Inline image: !testtest.jpe!' =>
175 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
175 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
176 'Inline image: !testtest.bmp!' =>
176 'Inline image: !testtest.bmp!' =>
177 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
177 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
178 }
178 }
179
179
180 attachments = [a1, a2, a3, a4]
180 attachments = [a1, a2, a3, a4]
181 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
181 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
182 end
182 end
183
183
184 def test_attached_images_should_read_later
184 def test_attached_images_should_read_later
185 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
185 set_fixtures_attachments_directory
186 a1 = Attachment.find(16)
186 a1 = Attachment.find(16)
187 assert_equal "testfile.png", a1.filename
187 assert_equal "testfile.png", a1.filename
188 assert a1.readable?
188 assert a1.readable?
189 assert (! a1.visible?(User.anonymous))
189 assert (! a1.visible?(User.anonymous))
190 assert a1.visible?(User.find(2))
190 assert a1.visible?(User.find(2))
191 a2 = Attachment.find(17)
191 a2 = Attachment.find(17)
192 assert_equal "testfile.PNG", a2.filename
192 assert_equal "testfile.PNG", a2.filename
193 assert a2.readable?
193 assert a2.readable?
194 assert (! a2.visible?(User.anonymous))
194 assert (! a2.visible?(User.anonymous))
195 assert a2.visible?(User.find(2))
195 assert a2.visible?(User.find(2))
196 assert a1.created_on < a2.created_on
196 assert a1.created_on < a2.created_on
197
197
198 to_test = {
198 to_test = {
199 'Inline image: !testfile.png!' =>
199 'Inline image: !testfile.png!' =>
200 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
200 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
201 'Inline image: !Testfile.PNG!' =>
201 'Inline image: !Testfile.PNG!' =>
202 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
202 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
203 }
203 }
204 attachments = [a1, a2]
204 attachments = [a1, a2]
205 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
205 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
206 set_tmp_attachments_directory
206 set_tmp_attachments_directory
207 end
207 end
208
208
209 def test_textile_external_links
209 def test_textile_external_links
210 to_test = {
210 to_test = {
211 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
211 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
212 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
212 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
213 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
213 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
214 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
214 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
215 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
215 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
216 # no multiline link text
216 # no multiline link text
217 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
217 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
218 # mailto link
218 # mailto link
219 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
219 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
220 # two exclamation marks
220 # two exclamation marks
221 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
221 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
222 # escaping
222 # escaping
223 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
223 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
224 }
224 }
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
226 end
226 end
227
227
228 def test_redmine_links
228 def test_redmine_links
229 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
229 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
230 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
230 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
231
231
232 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
232 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
233 :class => 'changeset', :title => 'My very first commit')
233 :class => 'changeset', :title => 'My very first commit')
234 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
234 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
235 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
235 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
236
236
237 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
237 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
238 :class => 'document')
238 :class => 'document')
239
239
240 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
240 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
241 :class => 'version')
241 :class => 'version')
242
242
243 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
243 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
244
244
245 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
245 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
246
246
247 news_url = {:controller => 'news', :action => 'show', :id => 1}
247 news_url = {:controller => 'news', :action => 'show', :id => 1}
248
248
249 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
249 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
250
250
251 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
251 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
252 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
252 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
253
253
254 to_test = {
254 to_test = {
255 # tickets
255 # tickets
256 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
256 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
257 # changesets
257 # changesets
258 'r1' => changeset_link,
258 'r1' => changeset_link,
259 'r1.' => "#{changeset_link}.",
259 'r1.' => "#{changeset_link}.",
260 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
260 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
261 'r1,r2' => "#{changeset_link},#{changeset_link2}",
261 'r1,r2' => "#{changeset_link},#{changeset_link2}",
262 # documents
262 # documents
263 'document#1' => document_link,
263 'document#1' => document_link,
264 'document:"Test document"' => document_link,
264 'document:"Test document"' => document_link,
265 # versions
265 # versions
266 'version#2' => version_link,
266 'version#2' => version_link,
267 'version:1.0' => version_link,
267 'version:1.0' => version_link,
268 'version:"1.0"' => version_link,
268 'version:"1.0"' => version_link,
269 # source
269 # source
270 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
270 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
271 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
271 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
272 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
272 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
273 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
273 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
274 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
274 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
275 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
275 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
276 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
276 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
277 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
277 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
278 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
278 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
279 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
279 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
280 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
280 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
281 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
281 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
282 # forum
282 # forum
283 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
283 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
284 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
284 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
285 # message
285 # message
286 'message#4' => link_to('Post 2', message_url, :class => 'message'),
286 'message#4' => link_to('Post 2', message_url, :class => 'message'),
287 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
287 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
288 # news
288 # news
289 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
289 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
290 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
290 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
291 # project
291 # project
292 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
292 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
293 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
293 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
294 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
294 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
295 # escaping
295 # escaping
296 '!#3.' => '#3.',
296 '!#3.' => '#3.',
297 '!r1' => 'r1',
297 '!r1' => 'r1',
298 '!document#1' => 'document#1',
298 '!document#1' => 'document#1',
299 '!document:"Test document"' => 'document:"Test document"',
299 '!document:"Test document"' => 'document:"Test document"',
300 '!version#2' => 'version#2',
300 '!version#2' => 'version#2',
301 '!version:1.0' => 'version:1.0',
301 '!version:1.0' => 'version:1.0',
302 '!version:"1.0"' => 'version:"1.0"',
302 '!version:"1.0"' => 'version:"1.0"',
303 '!source:/some/file' => 'source:/some/file',
303 '!source:/some/file' => 'source:/some/file',
304 # not found
304 # not found
305 '#0123456789' => '#0123456789',
305 '#0123456789' => '#0123456789',
306 # invalid expressions
306 # invalid expressions
307 'source:' => 'source:',
307 'source:' => 'source:',
308 # url hash
308 # url hash
309 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
309 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
310 }
310 }
311 @project = Project.find(1)
311 @project = Project.find(1)
312 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
312 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
313 end
313 end
314
314
315 def test_cross_project_redmine_links
315 def test_cross_project_redmine_links
316 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
316 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
317 :class => 'source')
317 :class => 'source')
318
318
319 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
319 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
320 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
320 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
321
321
322 to_test = {
322 to_test = {
323 # documents
323 # documents
324 'document:"Test document"' => 'document:"Test document"',
324 'document:"Test document"' => 'document:"Test document"',
325 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
325 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
326 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
326 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
327 # versions
327 # versions
328 'version:"1.0"' => 'version:"1.0"',
328 'version:"1.0"' => 'version:"1.0"',
329 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
329 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
330 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
330 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
331 # changeset
331 # changeset
332 'r2' => 'r2',
332 'r2' => 'r2',
333 'ecookbook:r2' => changeset_link,
333 'ecookbook:r2' => changeset_link,
334 'invalid:r2' => 'invalid:r2',
334 'invalid:r2' => 'invalid:r2',
335 # source
335 # source
336 'source:/some/file' => 'source:/some/file',
336 'source:/some/file' => 'source:/some/file',
337 'ecookbook:source:/some/file' => source_link,
337 'ecookbook:source:/some/file' => source_link,
338 'invalid:source:/some/file' => 'invalid:source:/some/file',
338 'invalid:source:/some/file' => 'invalid:source:/some/file',
339 }
339 }
340 @project = Project.find(3)
340 @project = Project.find(3)
341 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
341 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
342 end
342 end
343
343
344 def test_redmine_links_git_commit
344 def test_redmine_links_git_commit
345 changeset_link = link_to('abcd',
345 changeset_link = link_to('abcd',
346 {
346 {
347 :controller => 'repositories',
347 :controller => 'repositories',
348 :action => 'revision',
348 :action => 'revision',
349 :id => 'subproject1',
349 :id => 'subproject1',
350 :rev => 'abcd',
350 :rev => 'abcd',
351 },
351 },
352 :class => 'changeset', :title => 'test commit')
352 :class => 'changeset', :title => 'test commit')
353 to_test = {
353 to_test = {
354 'commit:abcd' => changeset_link,
354 'commit:abcd' => changeset_link,
355 }
355 }
356 @project = Project.find(3)
356 @project = Project.find(3)
357 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
357 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
358 assert r
358 assert r
359 c = Changeset.new(:repository => r,
359 c = Changeset.new(:repository => r,
360 :committed_on => Time.now,
360 :committed_on => Time.now,
361 :revision => 'abcd',
361 :revision => 'abcd',
362 :scmid => 'abcd',
362 :scmid => 'abcd',
363 :comments => 'test commit')
363 :comments => 'test commit')
364 assert( c.save )
364 assert( c.save )
365 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
365 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
366 end
366 end
367
367
368 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
368 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
369 def test_redmine_links_darcs_commit
369 def test_redmine_links_darcs_commit
370 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
370 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
371 {
371 {
372 :controller => 'repositories',
372 :controller => 'repositories',
373 :action => 'revision',
373 :action => 'revision',
374 :id => 'subproject1',
374 :id => 'subproject1',
375 :rev => '123',
375 :rev => '123',
376 },
376 },
377 :class => 'changeset', :title => 'test commit')
377 :class => 'changeset', :title => 'test commit')
378 to_test = {
378 to_test = {
379 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
379 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
380 }
380 }
381 @project = Project.find(3)
381 @project = Project.find(3)
382 r = Repository::Darcs.create!(
382 r = Repository::Darcs.create!(
383 :project => @project, :url => '/tmp/test/darcs',
383 :project => @project, :url => '/tmp/test/darcs',
384 :log_encoding => 'UTF-8')
384 :log_encoding => 'UTF-8')
385 assert r
385 assert r
386 c = Changeset.new(:repository => r,
386 c = Changeset.new(:repository => r,
387 :committed_on => Time.now,
387 :committed_on => Time.now,
388 :revision => '123',
388 :revision => '123',
389 :scmid => '20080308225258-98289-abcd456efg.gz',
389 :scmid => '20080308225258-98289-abcd456efg.gz',
390 :comments => 'test commit')
390 :comments => 'test commit')
391 assert( c.save )
391 assert( c.save )
392 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
392 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
393 end
393 end
394
394
395 def test_redmine_links_mercurial_commit
395 def test_redmine_links_mercurial_commit
396 changeset_link_rev = link_to('r123',
396 changeset_link_rev = link_to('r123',
397 {
397 {
398 :controller => 'repositories',
398 :controller => 'repositories',
399 :action => 'revision',
399 :action => 'revision',
400 :id => 'subproject1',
400 :id => 'subproject1',
401 :rev => '123' ,
401 :rev => '123' ,
402 },
402 },
403 :class => 'changeset', :title => 'test commit')
403 :class => 'changeset', :title => 'test commit')
404 changeset_link_commit = link_to('abcd',
404 changeset_link_commit = link_to('abcd',
405 {
405 {
406 :controller => 'repositories',
406 :controller => 'repositories',
407 :action => 'revision',
407 :action => 'revision',
408 :id => 'subproject1',
408 :id => 'subproject1',
409 :rev => 'abcd' ,
409 :rev => 'abcd' ,
410 },
410 },
411 :class => 'changeset', :title => 'test commit')
411 :class => 'changeset', :title => 'test commit')
412 to_test = {
412 to_test = {
413 'r123' => changeset_link_rev,
413 'r123' => changeset_link_rev,
414 'commit:abcd' => changeset_link_commit,
414 'commit:abcd' => changeset_link_commit,
415 }
415 }
416 @project = Project.find(3)
416 @project = Project.find(3)
417 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
417 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
418 assert r
418 assert r
419 c = Changeset.new(:repository => r,
419 c = Changeset.new(:repository => r,
420 :committed_on => Time.now,
420 :committed_on => Time.now,
421 :revision => '123',
421 :revision => '123',
422 :scmid => 'abcd',
422 :scmid => 'abcd',
423 :comments => 'test commit')
423 :comments => 'test commit')
424 assert( c.save )
424 assert( c.save )
425 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
425 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
426 end
426 end
427
427
428 def test_attachment_links
428 def test_attachment_links
429 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
429 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
430 to_test = {
430 to_test = {
431 'attachment:error281.txt' => attachment_link
431 'attachment:error281.txt' => attachment_link
432 }
432 }
433 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
433 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
434 end
434 end
435
435
436 def test_wiki_links
436 def test_wiki_links
437 to_test = {
437 to_test = {
438 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
438 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
439 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
439 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
440 # title content should be formatted
440 # title content should be formatted
441 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
441 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
442 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
442 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
443 # link with anchor
443 # link with anchor
444 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
444 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
445 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
445 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
446 # page that doesn't exist
446 # page that doesn't exist
447 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
447 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
448 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
448 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
449 # link to another project wiki
449 # link to another project wiki
450 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
450 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
451 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
451 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
452 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
452 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
453 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
453 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
454 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
454 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
455 # striked through link
455 # striked through link
456 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
456 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
457 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
457 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
458 # escaping
458 # escaping
459 '![[Another page|Page]]' => '[[Another page|Page]]',
459 '![[Another page|Page]]' => '[[Another page|Page]]',
460 # project does not exist
460 # project does not exist
461 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
461 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
462 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
462 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
463 }
463 }
464
464
465 @project = Project.find(1)
465 @project = Project.find(1)
466 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
466 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
467 end
467 end
468
468
469 def test_wiki_links_within_local_file_generation_context
469 def test_wiki_links_within_local_file_generation_context
470
470
471 to_test = {
471 to_test = {
472 # link to a page
472 # link to a page
473 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
473 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
474 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
474 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
475 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
475 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
476 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
476 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
477 # page that doesn't exist
477 # page that doesn't exist
478 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
478 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
479 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
479 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
480 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
480 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
481 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
481 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
482 }
482 }
483
483
484 @project = Project.find(1)
484 @project = Project.find(1)
485
485
486 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
486 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
487 end
487 end
488
488
489 def test_html_tags
489 def test_html_tags
490 to_test = {
490 to_test = {
491 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
491 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
492 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
492 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
493 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
493 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
494 # do not escape pre/code tags
494 # do not escape pre/code tags
495 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
495 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
496 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
496 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
497 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
497 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
498 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
498 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
499 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
499 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
500 # remove attributes except class
500 # remove attributes except class
501 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
501 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
502 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
502 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
503 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
503 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
504 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
504 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
505 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
505 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
506 # xss
506 # xss
507 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
507 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
508 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
508 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
509 }
509 }
510 to_test.each { |text, result| assert_equal result, textilizable(text) }
510 to_test.each { |text, result| assert_equal result, textilizable(text) }
511 end
511 end
512
512
513 def test_allowed_html_tags
513 def test_allowed_html_tags
514 to_test = {
514 to_test = {
515 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
515 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
516 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
516 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
517 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
517 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
518 }
518 }
519 to_test.each { |text, result| assert_equal result, textilizable(text) }
519 to_test.each { |text, result| assert_equal result, textilizable(text) }
520 end
520 end
521
521
522 def test_pre_tags
522 def test_pre_tags
523 raw = <<-RAW
523 raw = <<-RAW
524 Before
524 Before
525
525
526 <pre>
526 <pre>
527 <prepared-statement-cache-size>32</prepared-statement-cache-size>
527 <prepared-statement-cache-size>32</prepared-statement-cache-size>
528 </pre>
528 </pre>
529
529
530 After
530 After
531 RAW
531 RAW
532
532
533 expected = <<-EXPECTED
533 expected = <<-EXPECTED
534 <p>Before</p>
534 <p>Before</p>
535 <pre>
535 <pre>
536 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
536 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
537 </pre>
537 </pre>
538 <p>After</p>
538 <p>After</p>
539 EXPECTED
539 EXPECTED
540
540
541 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
541 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
542 end
542 end
543
543
544 def test_pre_content_should_not_parse_wiki_and_redmine_links
544 def test_pre_content_should_not_parse_wiki_and_redmine_links
545 raw = <<-RAW
545 raw = <<-RAW
546 [[CookBook documentation]]
546 [[CookBook documentation]]
547
547
548 #1
548 #1
549
549
550 <pre>
550 <pre>
551 [[CookBook documentation]]
551 [[CookBook documentation]]
552
552
553 #1
553 #1
554 </pre>
554 </pre>
555 RAW
555 RAW
556
556
557 expected = <<-EXPECTED
557 expected = <<-EXPECTED
558 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
558 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
559 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
559 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
560 <pre>
560 <pre>
561 [[CookBook documentation]]
561 [[CookBook documentation]]
562
562
563 #1
563 #1
564 </pre>
564 </pre>
565 EXPECTED
565 EXPECTED
566
566
567 @project = Project.find(1)
567 @project = Project.find(1)
568 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
568 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
569 end
569 end
570
570
571 def test_non_closing_pre_blocks_should_be_closed
571 def test_non_closing_pre_blocks_should_be_closed
572 raw = <<-RAW
572 raw = <<-RAW
573 <pre><code>
573 <pre><code>
574 RAW
574 RAW
575
575
576 expected = <<-EXPECTED
576 expected = <<-EXPECTED
577 <pre><code>
577 <pre><code>
578 </code></pre>
578 </code></pre>
579 EXPECTED
579 EXPECTED
580
580
581 @project = Project.find(1)
581 @project = Project.find(1)
582 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
582 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
583 end
583 end
584
584
585 def test_syntax_highlight
585 def test_syntax_highlight
586 raw = <<-RAW
586 raw = <<-RAW
587 <pre><code class="ruby">
587 <pre><code class="ruby">
588 # Some ruby code here
588 # Some ruby code here
589 </code></pre>
589 </code></pre>
590 RAW
590 RAW
591
591
592 expected = <<-EXPECTED
592 expected = <<-EXPECTED
593 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
593 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
594 </code></pre>
594 </code></pre>
595 EXPECTED
595 EXPECTED
596
596
597 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
597 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
598 end
598 end
599
599
600 def test_wiki_links_in_tables
600 def test_wiki_links_in_tables
601 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
601 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
602 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
602 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
603 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
603 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
604 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
604 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
605 }
605 }
606 @project = Project.find(1)
606 @project = Project.find(1)
607 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
607 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
608 end
608 end
609
609
610 def test_text_formatting
610 def test_text_formatting
611 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
611 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
612 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
612 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
613 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
613 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
614 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
614 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
615 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
615 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
616 }
616 }
617 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
617 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
618 end
618 end
619
619
620 def test_wiki_horizontal_rule
620 def test_wiki_horizontal_rule
621 assert_equal '<hr />', textilizable('---')
621 assert_equal '<hr />', textilizable('---')
622 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
622 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
623 end
623 end
624
624
625 def test_footnotes
625 def test_footnotes
626 raw = <<-RAW
626 raw = <<-RAW
627 This is some text[1].
627 This is some text[1].
628
628
629 fn1. This is the foot note
629 fn1. This is the foot note
630 RAW
630 RAW
631
631
632 expected = <<-EXPECTED
632 expected = <<-EXPECTED
633 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
633 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
634 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
634 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
635 EXPECTED
635 EXPECTED
636
636
637 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
637 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
638 end
638 end
639
639
640 def test_headings
640 def test_headings
641 raw = 'h1. Some heading'
641 raw = 'h1. Some heading'
642 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
642 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
643
643
644 assert_equal expected, textilizable(raw)
644 assert_equal expected, textilizable(raw)
645 end
645 end
646
646
647 def test_headings_with_special_chars
647 def test_headings_with_special_chars
648 # This test makes sure that the generated anchor names match the expected
648 # This test makes sure that the generated anchor names match the expected
649 # ones even if the heading text contains unconventional characters
649 # ones even if the heading text contains unconventional characters
650 raw = 'h1. Some heading related to version 0.5'
650 raw = 'h1. Some heading related to version 0.5'
651 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
651 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
652 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
652 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
653
653
654 assert_equal expected, textilizable(raw)
654 assert_equal expected, textilizable(raw)
655 end
655 end
656
656
657 def test_wiki_links_within_wiki_page_context
657 def test_wiki_links_within_wiki_page_context
658
658
659 page = WikiPage.find_by_title('Another_page' )
659 page = WikiPage.find_by_title('Another_page' )
660
660
661 to_test = {
661 to_test = {
662 # link to another page
662 # link to another page
663 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
663 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
664 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
664 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
665 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
665 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
666 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
666 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
667 # link to the current page
667 # link to the current page
668 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
668 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
669 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
669 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
670 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
670 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
671 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
671 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
672 # page that doesn't exist
672 # page that doesn't exist
673 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
673 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
674 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
674 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
675 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">Unknown page</a>',
675 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">Unknown page</a>',
676 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">404</a>',
676 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page#anchor" class="wiki-page new">404</a>',
677 }
677 }
678
678
679 @project = Project.find(1)
679 @project = Project.find(1)
680
680
681 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.generate!( :text => text, :page => page ), :text) }
681 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.generate!( :text => text, :page => page ), :text) }
682 end
682 end
683
683
684 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
684 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
685
685
686 to_test = {
686 to_test = {
687 # link to a page
687 # link to a page
688 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
688 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
689 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
689 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
690 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
690 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
691 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
691 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
692 # page that doesn't exist
692 # page that doesn't exist
693 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
693 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
694 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
694 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
695 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
695 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
696 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
696 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
697 }
697 }
698
698
699 @project = Project.find(1)
699 @project = Project.find(1)
700
700
701 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
701 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
702 end
702 end
703
703
704 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
704 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
705 page = WikiPage.generate!( :title => 'Page Title' )
705 page = WikiPage.generate!( :title => 'Page Title' )
706 content = WikiContent.generate!( :text => 'h1. Some heading', :page => page )
706 content = WikiContent.generate!( :text => 'h1. Some heading', :page => page )
707
707
708 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
708 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
709
709
710 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
710 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
711 end
711 end
712
712
713 def test_table_of_content
713 def test_table_of_content
714 raw = <<-RAW
714 raw = <<-RAW
715 {{toc}}
715 {{toc}}
716
716
717 h1. Title
717 h1. Title
718
718
719 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
719 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
720
720
721 h2. Subtitle with a [[Wiki]] link
721 h2. Subtitle with a [[Wiki]] link
722
722
723 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
723 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
724
724
725 h2. Subtitle with [[Wiki|another Wiki]] link
725 h2. Subtitle with [[Wiki|another Wiki]] link
726
726
727 h2. Subtitle with %{color:red}red text%
727 h2. Subtitle with %{color:red}red text%
728
728
729 <pre>
729 <pre>
730 some code
730 some code
731 </pre>
731 </pre>
732
732
733 h3. Subtitle with *some* _modifiers_
733 h3. Subtitle with *some* _modifiers_
734
734
735 h1. Another title
735 h1. Another title
736
736
737 h3. An "Internet link":http://www.redmine.org/ inside subtitle
737 h3. An "Internet link":http://www.redmine.org/ inside subtitle
738
738
739 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
739 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
740
740
741 RAW
741 RAW
742
742
743 expected = '<ul class="toc">' +
743 expected = '<ul class="toc">' +
744 '<li><a href="#Title">Title</a>' +
744 '<li><a href="#Title">Title</a>' +
745 '<ul>' +
745 '<ul>' +
746 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
746 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
747 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
747 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
748 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
748 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
749 '<ul>' +
749 '<ul>' +
750 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
750 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
751 '</ul>' +
751 '</ul>' +
752 '</li>' +
752 '</li>' +
753 '</ul>' +
753 '</ul>' +
754 '</li>' +
754 '</li>' +
755 '<li><a href="#Another-title">Another title</a>' +
755 '<li><a href="#Another-title">Another title</a>' +
756 '<ul>' +
756 '<ul>' +
757 '<li>' +
757 '<li>' +
758 '<ul>' +
758 '<ul>' +
759 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
759 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
760 '</ul>' +
760 '</ul>' +
761 '</li>' +
761 '</li>' +
762 '<li><a href="#Project-Name">Project Name</a></li>' +
762 '<li><a href="#Project-Name">Project Name</a></li>' +
763 '</ul>' +
763 '</ul>' +
764 '</li>' +
764 '</li>' +
765 '</ul>'
765 '</ul>'
766
766
767 @project = Project.find(1)
767 @project = Project.find(1)
768 assert textilizable(raw).gsub("\n", "").include?(expected)
768 assert textilizable(raw).gsub("\n", "").include?(expected)
769 end
769 end
770
770
771 def test_table_of_content_should_contain_included_page_headings
771 def test_table_of_content_should_contain_included_page_headings
772 raw = <<-RAW
772 raw = <<-RAW
773 {{toc}}
773 {{toc}}
774
774
775 h1. Included
775 h1. Included
776
776
777 {{include(Child_1)}}
777 {{include(Child_1)}}
778 RAW
778 RAW
779
779
780 expected = '<ul class="toc">' +
780 expected = '<ul class="toc">' +
781 '<li><a href="#Included">Included</a></li>' +
781 '<li><a href="#Included">Included</a></li>' +
782 '<li><a href="#Child-page-1">Child page 1</a></li>' +
782 '<li><a href="#Child-page-1">Child page 1</a></li>' +
783 '</ul>'
783 '</ul>'
784
784
785 @project = Project.find(1)
785 @project = Project.find(1)
786 assert textilizable(raw).gsub("\n", "").include?(expected)
786 assert textilizable(raw).gsub("\n", "").include?(expected)
787 end
787 end
788
788
789 def test_default_formatter
789 def test_default_formatter
790 Setting.text_formatting = 'unknown'
790 Setting.text_formatting = 'unknown'
791 text = 'a *link*: http://www.example.net/'
791 text = 'a *link*: http://www.example.net/'
792 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
792 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
793 Setting.text_formatting = 'textile'
793 Setting.text_formatting = 'textile'
794 end
794 end
795
795
796 def test_due_date_distance_in_words
796 def test_due_date_distance_in_words
797 to_test = { Date.today => 'Due in 0 days',
797 to_test = { Date.today => 'Due in 0 days',
798 Date.today + 1 => 'Due in 1 day',
798 Date.today + 1 => 'Due in 1 day',
799 Date.today + 100 => 'Due in about 3 months',
799 Date.today + 100 => 'Due in about 3 months',
800 Date.today + 20000 => 'Due in over 54 years',
800 Date.today + 20000 => 'Due in over 54 years',
801 Date.today - 1 => '1 day late',
801 Date.today - 1 => '1 day late',
802 Date.today - 100 => 'about 3 months late',
802 Date.today - 100 => 'about 3 months late',
803 Date.today - 20000 => 'over 54 years late',
803 Date.today - 20000 => 'over 54 years late',
804 }
804 }
805 ::I18n.locale = :en
805 ::I18n.locale = :en
806 to_test.each do |date, expected|
806 to_test.each do |date, expected|
807 assert_equal expected, due_date_distance_in_words(date)
807 assert_equal expected, due_date_distance_in_words(date)
808 end
808 end
809 end
809 end
810
810
811 def test_avatar
811 def test_avatar
812 # turn on avatars
812 # turn on avatars
813 Setting.gravatar_enabled = '1'
813 Setting.gravatar_enabled = '1'
814 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
814 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
815 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
815 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
816 assert_nil avatar('jsmith')
816 assert_nil avatar('jsmith')
817 assert_nil avatar(nil)
817 assert_nil avatar(nil)
818
818
819 # turn off avatars
819 # turn off avatars
820 Setting.gravatar_enabled = '0'
820 Setting.gravatar_enabled = '0'
821 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
821 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
822 end
822 end
823
823
824 def test_link_to_user
824 def test_link_to_user
825 user = User.find(2)
825 user = User.find(2)
826 t = link_to_user(user)
826 t = link_to_user(user)
827 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
827 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
828 end
828 end
829
829
830 def test_link_to_user_should_not_link_to_locked_user
830 def test_link_to_user_should_not_link_to_locked_user
831 user = User.find(5)
831 user = User.find(5)
832 assert user.locked?
832 assert user.locked?
833 t = link_to_user(user)
833 t = link_to_user(user)
834 assert_equal user.name, t
834 assert_equal user.name, t
835 end
835 end
836
836
837 def test_link_to_user_should_not_link_to_anonymous
837 def test_link_to_user_should_not_link_to_anonymous
838 user = User.anonymous
838 user = User.anonymous
839 assert user.anonymous?
839 assert user.anonymous?
840 t = link_to_user(user)
840 t = link_to_user(user)
841 assert_equal ::I18n.t(:label_user_anonymous), t
841 assert_equal ::I18n.t(:label_user_anonymous), t
842 end
842 end
843
843
844 def test_link_to_project
844 def test_link_to_project
845 project = Project.find(1)
845 project = Project.find(1)
846 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
846 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
847 link_to_project(project)
847 link_to_project(project)
848 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
848 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
849 link_to_project(project, :action => 'settings')
849 link_to_project(project, :action => 'settings')
850 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
850 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
851 link_to_project(project, {:only_path => false, :jump => 'blah'})
851 link_to_project(project, {:only_path => false, :jump => 'blah'})
852 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
852 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
853 link_to_project(project, {:action => 'settings'}, :class => "project")
853 link_to_project(project, {:action => 'settings'}, :class => "project")
854 end
854 end
855
855
856 def test_principals_options_for_select_with_users
856 def test_principals_options_for_select_with_users
857 users = [User.find(2), User.find(4)]
857 users = [User.find(2), User.find(4)]
858 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
858 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
859 principals_options_for_select(users)
859 principals_options_for_select(users)
860 end
860 end
861
861
862 def test_principals_options_for_select_with_selected
862 def test_principals_options_for_select_with_selected
863 users = [User.find(2), User.find(4)]
863 users = [User.find(2), User.find(4)]
864 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
864 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
865 principals_options_for_select(users, User.find(4))
865 principals_options_for_select(users, User.find(4))
866 end
866 end
867
867
868 def test_principals_options_for_select_with_users_and_groups
868 def test_principals_options_for_select_with_users_and_groups
869 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
869 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
870 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
870 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
871 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
871 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
872 principals_options_for_select(users)
872 principals_options_for_select(users)
873 end
873 end
874
874
875 def test_principals_options_for_select_with_empty_collection
875 def test_principals_options_for_select_with_empty_collection
876 assert_equal '', principals_options_for_select([])
876 assert_equal '', principals_options_for_select([])
877 end
877 end
878 end
878 end
@@ -1,129 +1,129
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require 'iconv'
19 require 'iconv'
20
20
21 class PdfTest < ActiveSupport::TestCase
21 class PdfTest < ActiveSupport::TestCase
22 fixtures :users, :projects, :roles, :members, :member_roles,
22 fixtures :users, :projects, :roles, :members, :member_roles,
23 :enabled_modules, :issues, :trackers, :attachments
23 :enabled_modules, :issues, :trackers, :attachments
24
24
25 def test_fix_text_encoding_nil
25 def test_fix_text_encoding_nil
26 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8")
26 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8")
27 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1")
27 assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1")
28 end
28 end
29
29
30 def test_rdm_pdf_iconv_cannot_convert_ja_cp932
30 def test_rdm_pdf_iconv_cannot_convert_ja_cp932
31 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
31 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
32 utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b"
32 utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b"
33 utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
33 utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
34 utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
34 utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
35 if utf8_txt_1.respond_to?(:force_encoding)
35 if utf8_txt_1.respond_to?(:force_encoding)
36 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
36 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
37 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
37 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
38 txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
38 txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
39 assert_equal "?\x91\xd4", txt_1
39 assert_equal "?\x91\xd4", txt_1
40 assert_equal "?\x91\xd4?", txt_2
40 assert_equal "?\x91\xd4?", txt_2
41 assert_equal "??\x91\xd4?", txt_3
41 assert_equal "??\x91\xd4?", txt_3
42 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
42 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
43 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
43 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
44 assert_equal "ASCII-8BIT", txt_3.encoding.to_s
44 assert_equal "ASCII-8BIT", txt_3.encoding.to_s
45 elsif RUBY_PLATFORM == 'java'
45 elsif RUBY_PLATFORM == 'java'
46 assert_equal "??",
46 assert_equal "??",
47 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
47 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
48 assert_equal "???",
48 assert_equal "???",
49 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
49 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
50 assert_equal "????",
50 assert_equal "????",
51 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
51 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
52 else
52 else
53 assert_equal "???\x91\xd4",
53 assert_equal "???\x91\xd4",
54 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
54 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
55 assert_equal "???\x91\xd4???",
55 assert_equal "???\x91\xd4???",
56 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
56 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
57 assert_equal "??????\x91\xd4???",
57 assert_equal "??????\x91\xd4???",
58 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
58 Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
59 end
59 end
60 end
60 end
61
61
62 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en
62 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en
63 str1 = "Texte encod\xe9 en ISO-8859-1"
63 str1 = "Texte encod\xe9 en ISO-8859-1"
64 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
64 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
65 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
65 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
66 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
66 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
67 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8')
67 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8')
68 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8')
68 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8')
69 if txt_1.respond_to?(:force_encoding)
69 if txt_1.respond_to?(:force_encoding)
70 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
70 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
71 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
71 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
72 end
72 end
73 assert_equal "Texte encod? en ISO-8859-1", txt_1
73 assert_equal "Texte encod? en ISO-8859-1", txt_1
74 assert_equal "?a?b?c?d?e test", txt_2
74 assert_equal "?a?b?c?d?e test", txt_2
75 end
75 end
76
76
77 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja
77 def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja
78 str1 = "Texte encod\xe9 en ISO-8859-1"
78 str1 = "Texte encod\xe9 en ISO-8859-1"
79 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
79 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
80 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
80 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
81 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
81 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
82 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
82 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
83 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding)
83 txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding)
84 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding)
84 txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding)
85 if txt_1.respond_to?(:force_encoding)
85 if txt_1.respond_to?(:force_encoding)
86 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
86 assert_equal "ASCII-8BIT", txt_1.encoding.to_s
87 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
87 assert_equal "ASCII-8BIT", txt_2.encoding.to_s
88 end
88 end
89 assert_equal "Texte encod? en ISO-8859-1", txt_1
89 assert_equal "Texte encod? en ISO-8859-1", txt_1
90 assert_equal "?a?b?c?d?e test", txt_2
90 assert_equal "?a?b?c?d?e test", txt_2
91 end
91 end
92
92
93 def test_attach
93 def test_attach
94 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
94 set_fixtures_attachments_directory
95
95
96 str2 = "\x83e\x83X\x83g"
96 str2 = "\x83e\x83X\x83g"
97 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
97 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
98 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
98 encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" )
99
99
100 a1 = Attachment.find(17)
100 a1 = Attachment.find(17)
101 a2 = Attachment.find(19)
101 a2 = Attachment.find(19)
102
102
103 User.current = User.find(1)
103 User.current = User.find(1)
104 assert a1.readable?
104 assert a1.readable?
105 assert a1.visible?
105 assert a1.visible?
106 assert a2.readable?
106 assert a2.readable?
107 assert a2.visible?
107 assert a2.visible?
108
108
109 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
109 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
110 assert_not_nil aa1
110 assert_not_nil aa1
111 assert_equal 17, aa1.id
111 assert_equal 17, aa1.id
112 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
112 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
113 assert_not_nil aa2
113 assert_not_nil aa2
114 assert_equal 19, aa2.id
114 assert_equal 19, aa2.id
115
115
116 User.current = nil
116 User.current = nil
117 assert a1.readable?
117 assert a1.readable?
118 assert (! a1.visible?)
118 assert (! a1.visible?)
119 assert a2.readable?
119 assert a2.readable?
120 assert (! a2.visible?)
120 assert (! a2.visible?)
121
121
122 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
122 aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8")
123 assert_equal nil, aa1
123 assert_equal nil, aa1
124 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
124 aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding)
125 assert_equal nil, aa2
125 assert_equal nil, aa2
126
126
127 set_tmp_attachments_directory
127 set_tmp_attachments_directory
128 end
128 end
129 end
129 end
@@ -1,1148 +1,1149
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
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 :workflows,
28 :workflows,
29 :versions,
29 :versions,
30 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
30 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
31 :groups_users,
31 :groups_users,
32 :boards
32 :boards
33
33
34 def setup
34 def setup
35 @ecookbook = Project.find(1)
35 @ecookbook = Project.find(1)
36 @ecookbook_sub1 = Project.find(3)
36 @ecookbook_sub1 = Project.find(3)
37 set_tmp_attachments_directory
37 User.current = nil
38 User.current = nil
38 end
39 end
39
40
40 should_validate_presence_of :name
41 should_validate_presence_of :name
41 should_validate_presence_of :identifier
42 should_validate_presence_of :identifier
42
43
43 should_validate_uniqueness_of :identifier
44 should_validate_uniqueness_of :identifier
44
45
45 context "associations" do
46 context "associations" do
46 should_have_many :members
47 should_have_many :members
47 should_have_many :users, :through => :members
48 should_have_many :users, :through => :members
48 should_have_many :member_principals
49 should_have_many :member_principals
49 should_have_many :principals, :through => :member_principals
50 should_have_many :principals, :through => :member_principals
50 should_have_many :enabled_modules
51 should_have_many :enabled_modules
51 should_have_many :issues
52 should_have_many :issues
52 should_have_many :issue_changes, :through => :issues
53 should_have_many :issue_changes, :through => :issues
53 should_have_many :versions
54 should_have_many :versions
54 should_have_many :time_entries
55 should_have_many :time_entries
55 should_have_many :queries
56 should_have_many :queries
56 should_have_many :documents
57 should_have_many :documents
57 should_have_many :news
58 should_have_many :news
58 should_have_many :issue_categories
59 should_have_many :issue_categories
59 should_have_many :boards
60 should_have_many :boards
60 should_have_many :changesets, :through => :repository
61 should_have_many :changesets, :through => :repository
61
62
62 should_have_one :repository
63 should_have_one :repository
63 should_have_one :wiki
64 should_have_one :wiki
64
65
65 should_have_and_belong_to_many :trackers
66 should_have_and_belong_to_many :trackers
66 should_have_and_belong_to_many :issue_custom_fields
67 should_have_and_belong_to_many :issue_custom_fields
67 end
68 end
68
69
69 def test_truth
70 def test_truth
70 assert_kind_of Project, @ecookbook
71 assert_kind_of Project, @ecookbook
71 assert_equal "eCookbook", @ecookbook.name
72 assert_equal "eCookbook", @ecookbook.name
72 end
73 end
73
74
74 def test_default_attributes
75 def test_default_attributes
75 with_settings :default_projects_public => '1' do
76 with_settings :default_projects_public => '1' do
76 assert_equal true, Project.new.is_public
77 assert_equal true, Project.new.is_public
77 assert_equal false, Project.new(:is_public => false).is_public
78 assert_equal false, Project.new(:is_public => false).is_public
78 end
79 end
79
80
80 with_settings :default_projects_public => '0' do
81 with_settings :default_projects_public => '0' do
81 assert_equal false, Project.new.is_public
82 assert_equal false, Project.new.is_public
82 assert_equal true, Project.new(:is_public => true).is_public
83 assert_equal true, Project.new(:is_public => true).is_public
83 end
84 end
84
85
85 with_settings :sequential_project_identifiers => '1' do
86 with_settings :sequential_project_identifiers => '1' do
86 assert !Project.new.identifier.blank?
87 assert !Project.new.identifier.blank?
87 assert Project.new(:identifier => '').identifier.blank?
88 assert Project.new(:identifier => '').identifier.blank?
88 end
89 end
89
90
90 with_settings :sequential_project_identifiers => '0' do
91 with_settings :sequential_project_identifiers => '0' do
91 assert Project.new.identifier.blank?
92 assert Project.new.identifier.blank?
92 assert !Project.new(:identifier => 'test').blank?
93 assert !Project.new(:identifier => 'test').blank?
93 end
94 end
94
95
95 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
96 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
96 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
97 assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
97 end
98 end
98
99
99 assert_equal Tracker.all, Project.new.trackers
100 assert_equal Tracker.all, Project.new.trackers
100 assert_equal Tracker.find(1, 3), Project.new(:tracker_ids => [1, 3]).trackers
101 assert_equal Tracker.find(1, 3), Project.new(:tracker_ids => [1, 3]).trackers
101 end
102 end
102
103
103 def test_update
104 def test_update
104 assert_equal "eCookbook", @ecookbook.name
105 assert_equal "eCookbook", @ecookbook.name
105 @ecookbook.name = "eCook"
106 @ecookbook.name = "eCook"
106 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
107 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
107 @ecookbook.reload
108 @ecookbook.reload
108 assert_equal "eCook", @ecookbook.name
109 assert_equal "eCook", @ecookbook.name
109 end
110 end
110
111
111 def test_validate_identifier
112 def test_validate_identifier
112 to_test = {"abc" => true,
113 to_test = {"abc" => true,
113 "ab12" => true,
114 "ab12" => true,
114 "ab-12" => true,
115 "ab-12" => true,
115 "12" => false,
116 "12" => false,
116 "new" => false}
117 "new" => false}
117
118
118 to_test.each do |identifier, valid|
119 to_test.each do |identifier, valid|
119 p = Project.new
120 p = Project.new
120 p.identifier = identifier
121 p.identifier = identifier
121 p.valid?
122 p.valid?
122 assert_equal valid, p.errors['identifier'].nil?
123 assert_equal valid, p.errors['identifier'].nil?
123 end
124 end
124 end
125 end
125
126
126 def test_members_should_be_active_users
127 def test_members_should_be_active_users
127 Project.all.each do |project|
128 Project.all.each do |project|
128 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
129 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
129 end
130 end
130 end
131 end
131
132
132 def test_users_should_be_active_users
133 def test_users_should_be_active_users
133 Project.all.each do |project|
134 Project.all.each do |project|
134 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
135 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
135 end
136 end
136 end
137 end
137
138
138 def test_archive
139 def test_archive
139 user = @ecookbook.members.first.user
140 user = @ecookbook.members.first.user
140 @ecookbook.archive
141 @ecookbook.archive
141 @ecookbook.reload
142 @ecookbook.reload
142
143
143 assert !@ecookbook.active?
144 assert !@ecookbook.active?
144 assert @ecookbook.archived?
145 assert @ecookbook.archived?
145 assert !user.projects.include?(@ecookbook)
146 assert !user.projects.include?(@ecookbook)
146 # Subproject are also archived
147 # Subproject are also archived
147 assert !@ecookbook.children.empty?
148 assert !@ecookbook.children.empty?
148 assert @ecookbook.descendants.active.empty?
149 assert @ecookbook.descendants.active.empty?
149 end
150 end
150
151
151 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
152 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
152 # Assign an issue of a project to a version of a child project
153 # Assign an issue of a project to a version of a child project
153 Issue.find(4).update_attribute :fixed_version_id, 4
154 Issue.find(4).update_attribute :fixed_version_id, 4
154
155
155 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
156 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
156 assert_equal false, @ecookbook.archive
157 assert_equal false, @ecookbook.archive
157 end
158 end
158 @ecookbook.reload
159 @ecookbook.reload
159 assert @ecookbook.active?
160 assert @ecookbook.active?
160 end
161 end
161
162
162 def test_unarchive
163 def test_unarchive
163 user = @ecookbook.members.first.user
164 user = @ecookbook.members.first.user
164 @ecookbook.archive
165 @ecookbook.archive
165 # A subproject of an archived project can not be unarchived
166 # A subproject of an archived project can not be unarchived
166 assert !@ecookbook_sub1.unarchive
167 assert !@ecookbook_sub1.unarchive
167
168
168 # Unarchive project
169 # Unarchive project
169 assert @ecookbook.unarchive
170 assert @ecookbook.unarchive
170 @ecookbook.reload
171 @ecookbook.reload
171 assert @ecookbook.active?
172 assert @ecookbook.active?
172 assert !@ecookbook.archived?
173 assert !@ecookbook.archived?
173 assert user.projects.include?(@ecookbook)
174 assert user.projects.include?(@ecookbook)
174 # Subproject can now be unarchived
175 # Subproject can now be unarchived
175 @ecookbook_sub1.reload
176 @ecookbook_sub1.reload
176 assert @ecookbook_sub1.unarchive
177 assert @ecookbook_sub1.unarchive
177 end
178 end
178
179
179 def test_destroy
180 def test_destroy
180 # 2 active members
181 # 2 active members
181 assert_equal 2, @ecookbook.members.size
182 assert_equal 2, @ecookbook.members.size
182 # and 1 is locked
183 # and 1 is locked
183 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
184 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
184 # some boards
185 # some boards
185 assert @ecookbook.boards.any?
186 assert @ecookbook.boards.any?
186
187
187 @ecookbook.destroy
188 @ecookbook.destroy
188 # make sure that the project non longer exists
189 # make sure that the project non longer exists
189 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
190 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
190 # make sure related data was removed
191 # make sure related data was removed
191 assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
192 assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
192 assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
193 assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
193 assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
194 assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
194 end
195 end
195
196
196 def test_destroying_root_projects_should_clear_data
197 def test_destroying_root_projects_should_clear_data
197 Project.roots.each do |root|
198 Project.roots.each do |root|
198 root.destroy
199 root.destroy
199 end
200 end
200
201
201 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
202 assert_equal 0, Project.count, "Projects were not deleted: #{Project.all.inspect}"
202 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
203 assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
203 assert_equal 0, MemberRole.count
204 assert_equal 0, MemberRole.count
204 assert_equal 0, Issue.count
205 assert_equal 0, Issue.count
205 assert_equal 0, Journal.count
206 assert_equal 0, Journal.count
206 assert_equal 0, JournalDetail.count
207 assert_equal 0, JournalDetail.count
207 assert_equal 0, Attachment.count
208 assert_equal 0, Attachment.count
208 assert_equal 0, EnabledModule.count
209 assert_equal 0, EnabledModule.count
209 assert_equal 0, IssueCategory.count
210 assert_equal 0, IssueCategory.count
210 assert_equal 0, IssueRelation.count
211 assert_equal 0, IssueRelation.count
211 assert_equal 0, Board.count
212 assert_equal 0, Board.count
212 assert_equal 0, Message.count
213 assert_equal 0, Message.count
213 assert_equal 0, News.count
214 assert_equal 0, News.count
214 assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL")
215 assert_equal 0, Query.count(:conditions => "project_id IS NOT NULL")
215 assert_equal 0, Repository.count
216 assert_equal 0, Repository.count
216 assert_equal 0, Changeset.count
217 assert_equal 0, Changeset.count
217 assert_equal 0, Change.count
218 assert_equal 0, Change.count
218 assert_equal 0, Comment.count
219 assert_equal 0, Comment.count
219 assert_equal 0, TimeEntry.count
220 assert_equal 0, TimeEntry.count
220 assert_equal 0, Version.count
221 assert_equal 0, Version.count
221 assert_equal 0, Watcher.count
222 assert_equal 0, Watcher.count
222 assert_equal 0, Wiki.count
223 assert_equal 0, Wiki.count
223 assert_equal 0, WikiPage.count
224 assert_equal 0, WikiPage.count
224 assert_equal 0, WikiContent.count
225 assert_equal 0, WikiContent.count
225 assert_equal 0, WikiContent::Version.count
226 assert_equal 0, WikiContent::Version.count
226 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size
227 assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size
227 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size
228 assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size
228 assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']})
229 assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']})
229 end
230 end
230
231
231 def test_move_an_orphan_project_to_a_root_project
232 def test_move_an_orphan_project_to_a_root_project
232 sub = Project.find(2)
233 sub = Project.find(2)
233 sub.set_parent! @ecookbook
234 sub.set_parent! @ecookbook
234 assert_equal @ecookbook.id, sub.parent.id
235 assert_equal @ecookbook.id, sub.parent.id
235 @ecookbook.reload
236 @ecookbook.reload
236 assert_equal 4, @ecookbook.children.size
237 assert_equal 4, @ecookbook.children.size
237 end
238 end
238
239
239 def test_move_an_orphan_project_to_a_subproject
240 def test_move_an_orphan_project_to_a_subproject
240 sub = Project.find(2)
241 sub = Project.find(2)
241 assert sub.set_parent!(@ecookbook_sub1)
242 assert sub.set_parent!(@ecookbook_sub1)
242 end
243 end
243
244
244 def test_move_a_root_project_to_a_project
245 def test_move_a_root_project_to_a_project
245 sub = @ecookbook
246 sub = @ecookbook
246 assert sub.set_parent!(Project.find(2))
247 assert sub.set_parent!(Project.find(2))
247 end
248 end
248
249
249 def test_should_not_move_a_project_to_its_children
250 def test_should_not_move_a_project_to_its_children
250 sub = @ecookbook
251 sub = @ecookbook
251 assert !(sub.set_parent!(Project.find(3)))
252 assert !(sub.set_parent!(Project.find(3)))
252 end
253 end
253
254
254 def test_set_parent_should_add_roots_in_alphabetical_order
255 def test_set_parent_should_add_roots_in_alphabetical_order
255 ProjectCustomField.delete_all
256 ProjectCustomField.delete_all
256 Project.delete_all
257 Project.delete_all
257 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
258 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
258 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
259 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
259 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
260 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
260 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
261 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
261
262
262 assert_equal 4, Project.count
263 assert_equal 4, Project.count
263 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
264 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
264 end
265 end
265
266
266 def test_set_parent_should_add_children_in_alphabetical_order
267 def test_set_parent_should_add_children_in_alphabetical_order
267 ProjectCustomField.delete_all
268 ProjectCustomField.delete_all
268 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
269 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
269 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
270 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
270 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
271 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
271 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
272 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
272 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
273 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
273
274
274 parent.reload
275 parent.reload
275 assert_equal 4, parent.children.size
276 assert_equal 4, parent.children.size
276 assert_equal parent.children.sort_by(&:name), parent.children
277 assert_equal parent.children.sort_by(&:name), parent.children
277 end
278 end
278
279
279 def test_rebuild_should_sort_children_alphabetically
280 def test_rebuild_should_sort_children_alphabetically
280 ProjectCustomField.delete_all
281 ProjectCustomField.delete_all
281 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
282 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
282 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
283 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
283 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
284 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
284 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
285 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
285 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
286 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
286
287
287 Project.update_all("lft = NULL, rgt = NULL")
288 Project.update_all("lft = NULL, rgt = NULL")
288 Project.rebuild!
289 Project.rebuild!
289
290
290 parent.reload
291 parent.reload
291 assert_equal 4, parent.children.size
292 assert_equal 4, parent.children.size
292 assert_equal parent.children.sort_by(&:name), parent.children
293 assert_equal parent.children.sort_by(&:name), parent.children
293 end
294 end
294
295
295
296
296 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
297 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
297 # Parent issue with a hierarchy project's fixed version
298 # Parent issue with a hierarchy project's fixed version
298 parent_issue = Issue.find(1)
299 parent_issue = Issue.find(1)
299 parent_issue.update_attribute(:fixed_version_id, 4)
300 parent_issue.update_attribute(:fixed_version_id, 4)
300 parent_issue.reload
301 parent_issue.reload
301 assert_equal 4, parent_issue.fixed_version_id
302 assert_equal 4, parent_issue.fixed_version_id
302
303
303 # Should keep fixed versions for the issues
304 # Should keep fixed versions for the issues
304 issue_with_local_fixed_version = Issue.find(5)
305 issue_with_local_fixed_version = Issue.find(5)
305 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
306 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
306 issue_with_local_fixed_version.reload
307 issue_with_local_fixed_version.reload
307 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
308 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
308
309
309 # Local issue with hierarchy fixed_version
310 # Local issue with hierarchy fixed_version
310 issue_with_hierarchy_fixed_version = Issue.find(13)
311 issue_with_hierarchy_fixed_version = Issue.find(13)
311 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
312 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
312 issue_with_hierarchy_fixed_version.reload
313 issue_with_hierarchy_fixed_version.reload
313 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
314 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
314
315
315 # Move project out of the issue's hierarchy
316 # Move project out of the issue's hierarchy
316 moved_project = Project.find(3)
317 moved_project = Project.find(3)
317 moved_project.set_parent!(Project.find(2))
318 moved_project.set_parent!(Project.find(2))
318 parent_issue.reload
319 parent_issue.reload
319 issue_with_local_fixed_version.reload
320 issue_with_local_fixed_version.reload
320 issue_with_hierarchy_fixed_version.reload
321 issue_with_hierarchy_fixed_version.reload
321
322
322 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
323 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
323 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
324 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
324 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
325 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
325 end
326 end
326
327
327 def test_parent
328 def test_parent
328 p = Project.find(6).parent
329 p = Project.find(6).parent
329 assert p.is_a?(Project)
330 assert p.is_a?(Project)
330 assert_equal 5, p.id
331 assert_equal 5, p.id
331 end
332 end
332
333
333 def test_ancestors
334 def test_ancestors
334 a = Project.find(6).ancestors
335 a = Project.find(6).ancestors
335 assert a.first.is_a?(Project)
336 assert a.first.is_a?(Project)
336 assert_equal [1, 5], a.collect(&:id)
337 assert_equal [1, 5], a.collect(&:id)
337 end
338 end
338
339
339 def test_root
340 def test_root
340 r = Project.find(6).root
341 r = Project.find(6).root
341 assert r.is_a?(Project)
342 assert r.is_a?(Project)
342 assert_equal 1, r.id
343 assert_equal 1, r.id
343 end
344 end
344
345
345 def test_children
346 def test_children
346 c = Project.find(1).children
347 c = Project.find(1).children
347 assert c.first.is_a?(Project)
348 assert c.first.is_a?(Project)
348 assert_equal [5, 3, 4], c.collect(&:id)
349 assert_equal [5, 3, 4], c.collect(&:id)
349 end
350 end
350
351
351 def test_descendants
352 def test_descendants
352 d = Project.find(1).descendants
353 d = Project.find(1).descendants
353 assert d.first.is_a?(Project)
354 assert d.first.is_a?(Project)
354 assert_equal [5, 6, 3, 4], d.collect(&:id)
355 assert_equal [5, 6, 3, 4], d.collect(&:id)
355 end
356 end
356
357
357 def test_allowed_parents_should_be_empty_for_non_member_user
358 def test_allowed_parents_should_be_empty_for_non_member_user
358 Role.non_member.add_permission!(:add_project)
359 Role.non_member.add_permission!(:add_project)
359 user = User.find(9)
360 user = User.find(9)
360 assert user.memberships.empty?
361 assert user.memberships.empty?
361 User.current = user
362 User.current = user
362 assert Project.new.allowed_parents.compact.empty?
363 assert Project.new.allowed_parents.compact.empty?
363 end
364 end
364
365
365 def test_allowed_parents_with_add_subprojects_permission
366 def test_allowed_parents_with_add_subprojects_permission
366 Role.find(1).remove_permission!(:add_project)
367 Role.find(1).remove_permission!(:add_project)
367 Role.find(1).add_permission!(:add_subprojects)
368 Role.find(1).add_permission!(:add_subprojects)
368 User.current = User.find(2)
369 User.current = User.find(2)
369 # new project
370 # new project
370 assert !Project.new.allowed_parents.include?(nil)
371 assert !Project.new.allowed_parents.include?(nil)
371 assert Project.new.allowed_parents.include?(Project.find(1))
372 assert Project.new.allowed_parents.include?(Project.find(1))
372 # existing root project
373 # existing root project
373 assert Project.find(1).allowed_parents.include?(nil)
374 assert Project.find(1).allowed_parents.include?(nil)
374 # existing child
375 # existing child
375 assert Project.find(3).allowed_parents.include?(Project.find(1))
376 assert Project.find(3).allowed_parents.include?(Project.find(1))
376 assert !Project.find(3).allowed_parents.include?(nil)
377 assert !Project.find(3).allowed_parents.include?(nil)
377 end
378 end
378
379
379 def test_allowed_parents_with_add_project_permission
380 def test_allowed_parents_with_add_project_permission
380 Role.find(1).add_permission!(:add_project)
381 Role.find(1).add_permission!(:add_project)
381 Role.find(1).remove_permission!(:add_subprojects)
382 Role.find(1).remove_permission!(:add_subprojects)
382 User.current = User.find(2)
383 User.current = User.find(2)
383 # new project
384 # new project
384 assert Project.new.allowed_parents.include?(nil)
385 assert Project.new.allowed_parents.include?(nil)
385 assert !Project.new.allowed_parents.include?(Project.find(1))
386 assert !Project.new.allowed_parents.include?(Project.find(1))
386 # existing root project
387 # existing root project
387 assert Project.find(1).allowed_parents.include?(nil)
388 assert Project.find(1).allowed_parents.include?(nil)
388 # existing child
389 # existing child
389 assert Project.find(3).allowed_parents.include?(Project.find(1))
390 assert Project.find(3).allowed_parents.include?(Project.find(1))
390 assert Project.find(3).allowed_parents.include?(nil)
391 assert Project.find(3).allowed_parents.include?(nil)
391 end
392 end
392
393
393 def test_allowed_parents_with_add_project_and_subprojects_permission
394 def test_allowed_parents_with_add_project_and_subprojects_permission
394 Role.find(1).add_permission!(:add_project)
395 Role.find(1).add_permission!(:add_project)
395 Role.find(1).add_permission!(:add_subprojects)
396 Role.find(1).add_permission!(:add_subprojects)
396 User.current = User.find(2)
397 User.current = User.find(2)
397 # new project
398 # new project
398 assert Project.new.allowed_parents.include?(nil)
399 assert Project.new.allowed_parents.include?(nil)
399 assert Project.new.allowed_parents.include?(Project.find(1))
400 assert Project.new.allowed_parents.include?(Project.find(1))
400 # existing root project
401 # existing root project
401 assert Project.find(1).allowed_parents.include?(nil)
402 assert Project.find(1).allowed_parents.include?(nil)
402 # existing child
403 # existing child
403 assert Project.find(3).allowed_parents.include?(Project.find(1))
404 assert Project.find(3).allowed_parents.include?(Project.find(1))
404 assert Project.find(3).allowed_parents.include?(nil)
405 assert Project.find(3).allowed_parents.include?(nil)
405 end
406 end
406
407
407 def test_users_by_role
408 def test_users_by_role
408 users_by_role = Project.find(1).users_by_role
409 users_by_role = Project.find(1).users_by_role
409 assert_kind_of Hash, users_by_role
410 assert_kind_of Hash, users_by_role
410 role = Role.find(1)
411 role = Role.find(1)
411 assert_kind_of Array, users_by_role[role]
412 assert_kind_of Array, users_by_role[role]
412 assert users_by_role[role].include?(User.find(2))
413 assert users_by_role[role].include?(User.find(2))
413 end
414 end
414
415
415 def test_rolled_up_trackers
416 def test_rolled_up_trackers
416 parent = Project.find(1)
417 parent = Project.find(1)
417 parent.trackers = Tracker.find([1,2])
418 parent.trackers = Tracker.find([1,2])
418 child = parent.children.find(3)
419 child = parent.children.find(3)
419
420
420 assert_equal [1, 2], parent.tracker_ids
421 assert_equal [1, 2], parent.tracker_ids
421 assert_equal [2, 3], child.trackers.collect(&:id)
422 assert_equal [2, 3], child.trackers.collect(&:id)
422
423
423 assert_kind_of Tracker, parent.rolled_up_trackers.first
424 assert_kind_of Tracker, parent.rolled_up_trackers.first
424 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
425 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
425
426
426 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
427 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
427 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
428 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
428 end
429 end
429
430
430 def test_rolled_up_trackers_should_ignore_archived_subprojects
431 def test_rolled_up_trackers_should_ignore_archived_subprojects
431 parent = Project.find(1)
432 parent = Project.find(1)
432 parent.trackers = Tracker.find([1,2])
433 parent.trackers = Tracker.find([1,2])
433 child = parent.children.find(3)
434 child = parent.children.find(3)
434 child.trackers = Tracker.find([1,3])
435 child.trackers = Tracker.find([1,3])
435 parent.children.each(&:archive)
436 parent.children.each(&:archive)
436
437
437 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
438 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
438 end
439 end
439
440
440 context "#rolled_up_versions" do
441 context "#rolled_up_versions" do
441 setup do
442 setup do
442 @project = Project.generate!
443 @project = Project.generate!
443 @parent_version_1 = Version.generate!(:project => @project)
444 @parent_version_1 = Version.generate!(:project => @project)
444 @parent_version_2 = Version.generate!(:project => @project)
445 @parent_version_2 = Version.generate!(:project => @project)
445 end
446 end
446
447
447 should "include the versions for the current project" do
448 should "include the versions for the current project" do
448 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
449 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
449 end
450 end
450
451
451 should "include versions for a subproject" do
452 should "include versions for a subproject" do
452 @subproject = Project.generate!
453 @subproject = Project.generate!
453 @subproject.set_parent!(@project)
454 @subproject.set_parent!(@project)
454 @subproject_version = Version.generate!(:project => @subproject)
455 @subproject_version = Version.generate!(:project => @subproject)
455
456
456 assert_same_elements [
457 assert_same_elements [
457 @parent_version_1,
458 @parent_version_1,
458 @parent_version_2,
459 @parent_version_2,
459 @subproject_version
460 @subproject_version
460 ], @project.rolled_up_versions
461 ], @project.rolled_up_versions
461 end
462 end
462
463
463 should "include versions for a sub-subproject" do
464 should "include versions for a sub-subproject" do
464 @subproject = Project.generate!
465 @subproject = Project.generate!
465 @subproject.set_parent!(@project)
466 @subproject.set_parent!(@project)
466 @sub_subproject = Project.generate!
467 @sub_subproject = Project.generate!
467 @sub_subproject.set_parent!(@subproject)
468 @sub_subproject.set_parent!(@subproject)
468 @sub_subproject_version = Version.generate!(:project => @sub_subproject)
469 @sub_subproject_version = Version.generate!(:project => @sub_subproject)
469
470
470 @project.reload
471 @project.reload
471
472
472 assert_same_elements [
473 assert_same_elements [
473 @parent_version_1,
474 @parent_version_1,
474 @parent_version_2,
475 @parent_version_2,
475 @sub_subproject_version
476 @sub_subproject_version
476 ], @project.rolled_up_versions
477 ], @project.rolled_up_versions
477 end
478 end
478
479
479 should "only check active projects" do
480 should "only check active projects" do
480 @subproject = Project.generate!
481 @subproject = Project.generate!
481 @subproject.set_parent!(@project)
482 @subproject.set_parent!(@project)
482 @subproject_version = Version.generate!(:project => @subproject)
483 @subproject_version = Version.generate!(:project => @subproject)
483 assert @subproject.archive
484 assert @subproject.archive
484
485
485 @project.reload
486 @project.reload
486
487
487 assert !@subproject.active?
488 assert !@subproject.active?
488 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
489 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
489 end
490 end
490 end
491 end
491
492
492 def test_shared_versions_none_sharing
493 def test_shared_versions_none_sharing
493 p = Project.find(5)
494 p = Project.find(5)
494 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
495 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
495 assert p.shared_versions.include?(v)
496 assert p.shared_versions.include?(v)
496 assert !p.children.first.shared_versions.include?(v)
497 assert !p.children.first.shared_versions.include?(v)
497 assert !p.root.shared_versions.include?(v)
498 assert !p.root.shared_versions.include?(v)
498 assert !p.siblings.first.shared_versions.include?(v)
499 assert !p.siblings.first.shared_versions.include?(v)
499 assert !p.root.siblings.first.shared_versions.include?(v)
500 assert !p.root.siblings.first.shared_versions.include?(v)
500 end
501 end
501
502
502 def test_shared_versions_descendants_sharing
503 def test_shared_versions_descendants_sharing
503 p = Project.find(5)
504 p = Project.find(5)
504 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
505 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
505 assert p.shared_versions.include?(v)
506 assert p.shared_versions.include?(v)
506 assert p.children.first.shared_versions.include?(v)
507 assert p.children.first.shared_versions.include?(v)
507 assert !p.root.shared_versions.include?(v)
508 assert !p.root.shared_versions.include?(v)
508 assert !p.siblings.first.shared_versions.include?(v)
509 assert !p.siblings.first.shared_versions.include?(v)
509 assert !p.root.siblings.first.shared_versions.include?(v)
510 assert !p.root.siblings.first.shared_versions.include?(v)
510 end
511 end
511
512
512 def test_shared_versions_hierarchy_sharing
513 def test_shared_versions_hierarchy_sharing
513 p = Project.find(5)
514 p = Project.find(5)
514 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
515 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
515 assert p.shared_versions.include?(v)
516 assert p.shared_versions.include?(v)
516 assert p.children.first.shared_versions.include?(v)
517 assert p.children.first.shared_versions.include?(v)
517 assert p.root.shared_versions.include?(v)
518 assert p.root.shared_versions.include?(v)
518 assert !p.siblings.first.shared_versions.include?(v)
519 assert !p.siblings.first.shared_versions.include?(v)
519 assert !p.root.siblings.first.shared_versions.include?(v)
520 assert !p.root.siblings.first.shared_versions.include?(v)
520 end
521 end
521
522
522 def test_shared_versions_tree_sharing
523 def test_shared_versions_tree_sharing
523 p = Project.find(5)
524 p = Project.find(5)
524 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
525 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
525 assert p.shared_versions.include?(v)
526 assert p.shared_versions.include?(v)
526 assert p.children.first.shared_versions.include?(v)
527 assert p.children.first.shared_versions.include?(v)
527 assert p.root.shared_versions.include?(v)
528 assert p.root.shared_versions.include?(v)
528 assert p.siblings.first.shared_versions.include?(v)
529 assert p.siblings.first.shared_versions.include?(v)
529 assert !p.root.siblings.first.shared_versions.include?(v)
530 assert !p.root.siblings.first.shared_versions.include?(v)
530 end
531 end
531
532
532 def test_shared_versions_system_sharing
533 def test_shared_versions_system_sharing
533 p = Project.find(5)
534 p = Project.find(5)
534 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
535 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
535 assert p.shared_versions.include?(v)
536 assert p.shared_versions.include?(v)
536 assert p.children.first.shared_versions.include?(v)
537 assert p.children.first.shared_versions.include?(v)
537 assert p.root.shared_versions.include?(v)
538 assert p.root.shared_versions.include?(v)
538 assert p.siblings.first.shared_versions.include?(v)
539 assert p.siblings.first.shared_versions.include?(v)
539 assert p.root.siblings.first.shared_versions.include?(v)
540 assert p.root.siblings.first.shared_versions.include?(v)
540 end
541 end
541
542
542 def test_shared_versions
543 def test_shared_versions
543 parent = Project.find(1)
544 parent = Project.find(1)
544 child = parent.children.find(3)
545 child = parent.children.find(3)
545 private_child = parent.children.find(5)
546 private_child = parent.children.find(5)
546
547
547 assert_equal [1,2,3], parent.version_ids.sort
548 assert_equal [1,2,3], parent.version_ids.sort
548 assert_equal [4], child.version_ids
549 assert_equal [4], child.version_ids
549 assert_equal [6], private_child.version_ids
550 assert_equal [6], private_child.version_ids
550 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
551 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
551
552
552 assert_equal 6, parent.shared_versions.size
553 assert_equal 6, parent.shared_versions.size
553 parent.shared_versions.each do |version|
554 parent.shared_versions.each do |version|
554 assert_kind_of Version, version
555 assert_kind_of Version, version
555 end
556 end
556
557
557 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
558 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
558 end
559 end
559
560
560 def test_shared_versions_should_ignore_archived_subprojects
561 def test_shared_versions_should_ignore_archived_subprojects
561 parent = Project.find(1)
562 parent = Project.find(1)
562 child = parent.children.find(3)
563 child = parent.children.find(3)
563 child.archive
564 child.archive
564 parent.reload
565 parent.reload
565
566
566 assert_equal [1,2,3], parent.version_ids.sort
567 assert_equal [1,2,3], parent.version_ids.sort
567 assert_equal [4], child.version_ids
568 assert_equal [4], child.version_ids
568 assert !parent.shared_versions.collect(&:id).include?(4)
569 assert !parent.shared_versions.collect(&:id).include?(4)
569 end
570 end
570
571
571 def test_shared_versions_visible_to_user
572 def test_shared_versions_visible_to_user
572 user = User.find(3)
573 user = User.find(3)
573 parent = Project.find(1)
574 parent = Project.find(1)
574 child = parent.children.find(5)
575 child = parent.children.find(5)
575
576
576 assert_equal [1,2,3], parent.version_ids.sort
577 assert_equal [1,2,3], parent.version_ids.sort
577 assert_equal [6], child.version_ids
578 assert_equal [6], child.version_ids
578
579
579 versions = parent.shared_versions.visible(user)
580 versions = parent.shared_versions.visible(user)
580
581
581 assert_equal 4, versions.size
582 assert_equal 4, versions.size
582 versions.each do |version|
583 versions.each do |version|
583 assert_kind_of Version, version
584 assert_kind_of Version, version
584 end
585 end
585
586
586 assert !versions.collect(&:id).include?(6)
587 assert !versions.collect(&:id).include?(6)
587 end
588 end
588
589
589 def test_next_identifier
590 def test_next_identifier
590 ProjectCustomField.delete_all
591 ProjectCustomField.delete_all
591 Project.create!(:name => 'last', :identifier => 'p2008040')
592 Project.create!(:name => 'last', :identifier => 'p2008040')
592 assert_equal 'p2008041', Project.next_identifier
593 assert_equal 'p2008041', Project.next_identifier
593 end
594 end
594
595
595 def test_next_identifier_first_project
596 def test_next_identifier_first_project
596 Project.delete_all
597 Project.delete_all
597 assert_nil Project.next_identifier
598 assert_nil Project.next_identifier
598 end
599 end
599
600
600 def test_enabled_module_names
601 def test_enabled_module_names
601 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
602 with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
602 project = Project.new
603 project = Project.new
603
604
604 project.enabled_module_names = %w(issue_tracking news)
605 project.enabled_module_names = %w(issue_tracking news)
605 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
606 assert_equal %w(issue_tracking news), project.enabled_module_names.sort
606 end
607 end
607 end
608 end
608
609
609 context "enabled_modules" do
610 context "enabled_modules" do
610 setup do
611 setup do
611 @project = Project.find(1)
612 @project = Project.find(1)
612 end
613 end
613
614
614 should "define module by names and preserve ids" do
615 should "define module by names and preserve ids" do
615 # Remove one module
616 # Remove one module
616 modules = @project.enabled_modules.slice(0..-2)
617 modules = @project.enabled_modules.slice(0..-2)
617 assert modules.any?
618 assert modules.any?
618 assert_difference 'EnabledModule.count', -1 do
619 assert_difference 'EnabledModule.count', -1 do
619 @project.enabled_module_names = modules.collect(&:name)
620 @project.enabled_module_names = modules.collect(&:name)
620 end
621 end
621 @project.reload
622 @project.reload
622 # Ids should be preserved
623 # Ids should be preserved
623 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
624 assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
624 end
625 end
625
626
626 should "enable a module" do
627 should "enable a module" do
627 @project.enabled_module_names = []
628 @project.enabled_module_names = []
628 @project.reload
629 @project.reload
629 assert_equal [], @project.enabled_module_names
630 assert_equal [], @project.enabled_module_names
630 #with string
631 #with string
631 @project.enable_module!("issue_tracking")
632 @project.enable_module!("issue_tracking")
632 assert_equal ["issue_tracking"], @project.enabled_module_names
633 assert_equal ["issue_tracking"], @project.enabled_module_names
633 #with symbol
634 #with symbol
634 @project.enable_module!(:gantt)
635 @project.enable_module!(:gantt)
635 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
636 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
636 #don't add a module twice
637 #don't add a module twice
637 @project.enable_module!("issue_tracking")
638 @project.enable_module!("issue_tracking")
638 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
639 assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
639 end
640 end
640
641
641 should "disable a module" do
642 should "disable a module" do
642 #with string
643 #with string
643 assert @project.enabled_module_names.include?("issue_tracking")
644 assert @project.enabled_module_names.include?("issue_tracking")
644 @project.disable_module!("issue_tracking")
645 @project.disable_module!("issue_tracking")
645 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
646 assert ! @project.reload.enabled_module_names.include?("issue_tracking")
646 #with symbol
647 #with symbol
647 assert @project.enabled_module_names.include?("gantt")
648 assert @project.enabled_module_names.include?("gantt")
648 @project.disable_module!(:gantt)
649 @project.disable_module!(:gantt)
649 assert ! @project.reload.enabled_module_names.include?("gantt")
650 assert ! @project.reload.enabled_module_names.include?("gantt")
650 #with EnabledModule object
651 #with EnabledModule object
651 first_module = @project.enabled_modules.first
652 first_module = @project.enabled_modules.first
652 @project.disable_module!(first_module)
653 @project.disable_module!(first_module)
653 assert ! @project.reload.enabled_module_names.include?(first_module.name)
654 assert ! @project.reload.enabled_module_names.include?(first_module.name)
654 end
655 end
655 end
656 end
656
657
657 def test_enabled_module_names_should_not_recreate_enabled_modules
658 def test_enabled_module_names_should_not_recreate_enabled_modules
658 project = Project.find(1)
659 project = Project.find(1)
659 # Remove one module
660 # Remove one module
660 modules = project.enabled_modules.slice(0..-2)
661 modules = project.enabled_modules.slice(0..-2)
661 assert modules.any?
662 assert modules.any?
662 assert_difference 'EnabledModule.count', -1 do
663 assert_difference 'EnabledModule.count', -1 do
663 project.enabled_module_names = modules.collect(&:name)
664 project.enabled_module_names = modules.collect(&:name)
664 end
665 end
665 project.reload
666 project.reload
666 # Ids should be preserved
667 # Ids should be preserved
667 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
668 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
668 end
669 end
669
670
670 def test_copy_from_existing_project
671 def test_copy_from_existing_project
671 source_project = Project.find(1)
672 source_project = Project.find(1)
672 copied_project = Project.copy_from(1)
673 copied_project = Project.copy_from(1)
673
674
674 assert copied_project
675 assert copied_project
675 # Cleared attributes
676 # Cleared attributes
676 assert copied_project.id.blank?
677 assert copied_project.id.blank?
677 assert copied_project.name.blank?
678 assert copied_project.name.blank?
678 assert copied_project.identifier.blank?
679 assert copied_project.identifier.blank?
679
680
680 # Duplicated attributes
681 # Duplicated attributes
681 assert_equal source_project.description, copied_project.description
682 assert_equal source_project.description, copied_project.description
682 assert_equal source_project.enabled_modules, copied_project.enabled_modules
683 assert_equal source_project.enabled_modules, copied_project.enabled_modules
683 assert_equal source_project.trackers, copied_project.trackers
684 assert_equal source_project.trackers, copied_project.trackers
684
685
685 # Default attributes
686 # Default attributes
686 assert_equal 1, copied_project.status
687 assert_equal 1, copied_project.status
687 end
688 end
688
689
689 def test_activities_should_use_the_system_activities
690 def test_activities_should_use_the_system_activities
690 project = Project.find(1)
691 project = Project.find(1)
691 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
692 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
692 end
693 end
693
694
694
695
695 def test_activities_should_use_the_project_specific_activities
696 def test_activities_should_use_the_project_specific_activities
696 project = Project.find(1)
697 project = Project.find(1)
697 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
698 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
698 assert overridden_activity.save!
699 assert overridden_activity.save!
699
700
700 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
701 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
701 end
702 end
702
703
703 def test_activities_should_not_include_the_inactive_project_specific_activities
704 def test_activities_should_not_include_the_inactive_project_specific_activities
704 project = Project.find(1)
705 project = Project.find(1)
705 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
706 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
706 assert overridden_activity.save!
707 assert overridden_activity.save!
707
708
708 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
709 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
709 end
710 end
710
711
711 def test_activities_should_not_include_project_specific_activities_from_other_projects
712 def test_activities_should_not_include_project_specific_activities_from_other_projects
712 project = Project.find(1)
713 project = Project.find(1)
713 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
714 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
714 assert overridden_activity.save!
715 assert overridden_activity.save!
715
716
716 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
717 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
717 end
718 end
718
719
719 def test_activities_should_handle_nils
720 def test_activities_should_handle_nils
720 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
721 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
721 TimeEntryActivity.delete_all
722 TimeEntryActivity.delete_all
722
723
723 # No activities
724 # No activities
724 project = Project.find(1)
725 project = Project.find(1)
725 assert project.activities.empty?
726 assert project.activities.empty?
726
727
727 # No system, one overridden
728 # No system, one overridden
728 assert overridden_activity.save!
729 assert overridden_activity.save!
729 project.reload
730 project.reload
730 assert_equal [overridden_activity], project.activities
731 assert_equal [overridden_activity], project.activities
731 end
732 end
732
733
733 def test_activities_should_override_system_activities_with_project_activities
734 def test_activities_should_override_system_activities_with_project_activities
734 project = Project.find(1)
735 project = Project.find(1)
735 parent_activity = TimeEntryActivity.find(:first)
736 parent_activity = TimeEntryActivity.find(:first)
736 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
737 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
737 assert overridden_activity.save!
738 assert overridden_activity.save!
738
739
739 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
740 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
740 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
741 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
741 end
742 end
742
743
743 def test_activities_should_include_inactive_activities_if_specified
744 def test_activities_should_include_inactive_activities_if_specified
744 project = Project.find(1)
745 project = Project.find(1)
745 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
746 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
746 assert overridden_activity.save!
747 assert overridden_activity.save!
747
748
748 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
749 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
749 end
750 end
750
751
751 test 'activities should not include active System activities if the project has an override that is inactive' do
752 test 'activities should not include active System activities if the project has an override that is inactive' do
752 project = Project.find(1)
753 project = Project.find(1)
753 system_activity = TimeEntryActivity.find_by_name('Design')
754 system_activity = TimeEntryActivity.find_by_name('Design')
754 assert system_activity.active?
755 assert system_activity.active?
755 overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
756 overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
756 assert overridden_activity.save!
757 assert overridden_activity.save!
757
758
758 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
759 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
759 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
760 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
760 end
761 end
761
762
762 def test_close_completed_versions
763 def test_close_completed_versions
763 Version.update_all("status = 'open'")
764 Version.update_all("status = 'open'")
764 project = Project.find(1)
765 project = Project.find(1)
765 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
766 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
766 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
767 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
767 project.close_completed_versions
768 project.close_completed_versions
768 project.reload
769 project.reload
769 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
770 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
770 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
771 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
771 end
772 end
772
773
773 context "Project#copy" do
774 context "Project#copy" do
774 setup do
775 setup do
775 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
776 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
776 Project.destroy_all :identifier => "copy-test"
777 Project.destroy_all :identifier => "copy-test"
777 @source_project = Project.find(2)
778 @source_project = Project.find(2)
778 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
779 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
779 @project.trackers = @source_project.trackers
780 @project.trackers = @source_project.trackers
780 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
781 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
781 end
782 end
782
783
783 should "copy issues" do
784 should "copy issues" do
784 @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
785 @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
785 :subject => "copy issue status",
786 :subject => "copy issue status",
786 :tracker_id => 1,
787 :tracker_id => 1,
787 :assigned_to_id => 2,
788 :assigned_to_id => 2,
788 :project_id => @source_project.id)
789 :project_id => @source_project.id)
789 assert @project.valid?
790 assert @project.valid?
790 assert @project.issues.empty?
791 assert @project.issues.empty?
791 assert @project.copy(@source_project)
792 assert @project.copy(@source_project)
792
793
793 assert_equal @source_project.issues.size, @project.issues.size
794 assert_equal @source_project.issues.size, @project.issues.size
794 @project.issues.each do |issue|
795 @project.issues.each do |issue|
795 assert issue.valid?
796 assert issue.valid?
796 assert ! issue.assigned_to.blank?
797 assert ! issue.assigned_to.blank?
797 assert_equal @project, issue.project
798 assert_equal @project, issue.project
798 end
799 end
799
800
800 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
801 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
801 assert copied_issue
802 assert copied_issue
802 assert copied_issue.status
803 assert copied_issue.status
803 assert_equal "Closed", copied_issue.status.name
804 assert_equal "Closed", copied_issue.status.name
804 end
805 end
805
806
806 should "change the new issues to use the copied version" do
807 should "change the new issues to use the copied version" do
807 User.current = User.find(1)
808 User.current = User.find(1)
808 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
809 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
809 @source_project.versions << assigned_version
810 @source_project.versions << assigned_version
810 assert_equal 3, @source_project.versions.size
811 assert_equal 3, @source_project.versions.size
811 Issue.generate_for_project!(@source_project,
812 Issue.generate_for_project!(@source_project,
812 :fixed_version_id => assigned_version.id,
813 :fixed_version_id => assigned_version.id,
813 :subject => "change the new issues to use the copied version",
814 :subject => "change the new issues to use the copied version",
814 :tracker_id => 1,
815 :tracker_id => 1,
815 :project_id => @source_project.id)
816 :project_id => @source_project.id)
816
817
817 assert @project.copy(@source_project)
818 assert @project.copy(@source_project)
818 @project.reload
819 @project.reload
819 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
820 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
820
821
821 assert copied_issue
822 assert copied_issue
822 assert copied_issue.fixed_version
823 assert copied_issue.fixed_version
823 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
824 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
824 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
825 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
825 end
826 end
826
827
827 should "copy issue relations" do
828 should "copy issue relations" do
828 Setting.cross_project_issue_relations = '1'
829 Setting.cross_project_issue_relations = '1'
829
830
830 second_issue = Issue.generate!(:status_id => 5,
831 second_issue = Issue.generate!(:status_id => 5,
831 :subject => "copy issue relation",
832 :subject => "copy issue relation",
832 :tracker_id => 1,
833 :tracker_id => 1,
833 :assigned_to_id => 2,
834 :assigned_to_id => 2,
834 :project_id => @source_project.id)
835 :project_id => @source_project.id)
835 source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
836 source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
836 :issue_to => second_issue,
837 :issue_to => second_issue,
837 :relation_type => "relates")
838 :relation_type => "relates")
838 source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
839 source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
839 :issue_to => second_issue,
840 :issue_to => second_issue,
840 :relation_type => "duplicates")
841 :relation_type => "duplicates")
841
842
842 assert @project.copy(@source_project)
843 assert @project.copy(@source_project)
843 assert_equal @source_project.issues.count, @project.issues.count
844 assert_equal @source_project.issues.count, @project.issues.count
844 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
845 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
845 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
846 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
846
847
847 # First issue with a relation on project
848 # First issue with a relation on project
848 assert_equal 1, copied_issue.relations.size, "Relation not copied"
849 assert_equal 1, copied_issue.relations.size, "Relation not copied"
849 copied_relation = copied_issue.relations.first
850 copied_relation = copied_issue.relations.first
850 assert_equal "relates", copied_relation.relation_type
851 assert_equal "relates", copied_relation.relation_type
851 assert_equal copied_second_issue.id, copied_relation.issue_to_id
852 assert_equal copied_second_issue.id, copied_relation.issue_to_id
852 assert_not_equal source_relation.id, copied_relation.id
853 assert_not_equal source_relation.id, copied_relation.id
853
854
854 # Second issue with a cross project relation
855 # Second issue with a cross project relation
855 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
856 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
856 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
857 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
857 assert_equal "duplicates", copied_relation.relation_type
858 assert_equal "duplicates", copied_relation.relation_type
858 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
859 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
859 assert_not_equal source_relation_cross_project.id, copied_relation.id
860 assert_not_equal source_relation_cross_project.id, copied_relation.id
860 end
861 end
861
862
862 should "copy memberships" do
863 should "copy memberships" do
863 assert @project.valid?
864 assert @project.valid?
864 assert @project.members.empty?
865 assert @project.members.empty?
865 assert @project.copy(@source_project)
866 assert @project.copy(@source_project)
866
867
867 assert_equal @source_project.memberships.size, @project.memberships.size
868 assert_equal @source_project.memberships.size, @project.memberships.size
868 @project.memberships.each do |membership|
869 @project.memberships.each do |membership|
869 assert membership
870 assert membership
870 assert_equal @project, membership.project
871 assert_equal @project, membership.project
871 end
872 end
872 end
873 end
873
874
874 should "copy memberships with groups and additional roles" do
875 should "copy memberships with groups and additional roles" do
875 group = Group.create!(:lastname => "Copy group")
876 group = Group.create!(:lastname => "Copy group")
876 user = User.find(7)
877 user = User.find(7)
877 group.users << user
878 group.users << user
878 # group role
879 # group role
879 Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
880 Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
880 member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
881 member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
881 # additional role
882 # additional role
882 member.role_ids = [1]
883 member.role_ids = [1]
883
884
884 assert @project.copy(@source_project)
885 assert @project.copy(@source_project)
885 member = Member.find_by_user_id_and_project_id(user.id, @project.id)
886 member = Member.find_by_user_id_and_project_id(user.id, @project.id)
886 assert_not_nil member
887 assert_not_nil member
887 assert_equal [1, 2], member.role_ids.sort
888 assert_equal [1, 2], member.role_ids.sort
888 end
889 end
889
890
890 should "copy project specific queries" do
891 should "copy project specific queries" do
891 assert @project.valid?
892 assert @project.valid?
892 assert @project.queries.empty?
893 assert @project.queries.empty?
893 assert @project.copy(@source_project)
894 assert @project.copy(@source_project)
894
895
895 assert_equal @source_project.queries.size, @project.queries.size
896 assert_equal @source_project.queries.size, @project.queries.size
896 @project.queries.each do |query|
897 @project.queries.each do |query|
897 assert query
898 assert query
898 assert_equal @project, query.project
899 assert_equal @project, query.project
899 end
900 end
900 assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
901 assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
901 end
902 end
902
903
903 should "copy versions" do
904 should "copy versions" do
904 @source_project.versions << Version.generate!
905 @source_project.versions << Version.generate!
905 @source_project.versions << Version.generate!
906 @source_project.versions << Version.generate!
906
907
907 assert @project.versions.empty?
908 assert @project.versions.empty?
908 assert @project.copy(@source_project)
909 assert @project.copy(@source_project)
909
910
910 assert_equal @source_project.versions.size, @project.versions.size
911 assert_equal @source_project.versions.size, @project.versions.size
911 @project.versions.each do |version|
912 @project.versions.each do |version|
912 assert version
913 assert version
913 assert_equal @project, version.project
914 assert_equal @project, version.project
914 end
915 end
915 end
916 end
916
917
917 should "copy wiki" do
918 should "copy wiki" do
918 assert_difference 'Wiki.count' do
919 assert_difference 'Wiki.count' do
919 assert @project.copy(@source_project)
920 assert @project.copy(@source_project)
920 end
921 end
921
922
922 assert @project.wiki
923 assert @project.wiki
923 assert_not_equal @source_project.wiki, @project.wiki
924 assert_not_equal @source_project.wiki, @project.wiki
924 assert_equal "Start page", @project.wiki.start_page
925 assert_equal "Start page", @project.wiki.start_page
925 end
926 end
926
927
927 should "copy wiki pages and content with hierarchy" do
928 should "copy wiki pages and content with hierarchy" do
928 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
929 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
929 assert @project.copy(@source_project)
930 assert @project.copy(@source_project)
930 end
931 end
931
932
932 assert @project.wiki
933 assert @project.wiki
933 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
934 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
934
935
935 @project.wiki.pages.each do |wiki_page|
936 @project.wiki.pages.each do |wiki_page|
936 assert wiki_page.content
937 assert wiki_page.content
937 assert !@source_project.wiki.pages.include?(wiki_page)
938 assert !@source_project.wiki.pages.include?(wiki_page)
938 end
939 end
939
940
940 parent = @project.wiki.find_page('Parent_page')
941 parent = @project.wiki.find_page('Parent_page')
941 child1 = @project.wiki.find_page('Child_page_1')
942 child1 = @project.wiki.find_page('Child_page_1')
942 child2 = @project.wiki.find_page('Child_page_2')
943 child2 = @project.wiki.find_page('Child_page_2')
943 assert_equal parent, child1.parent
944 assert_equal parent, child1.parent
944 assert_equal parent, child2.parent
945 assert_equal parent, child2.parent
945 end
946 end
946
947
947 should "copy issue categories" do
948 should "copy issue categories" do
948 assert @project.copy(@source_project)
949 assert @project.copy(@source_project)
949
950
950 assert_equal 2, @project.issue_categories.size
951 assert_equal 2, @project.issue_categories.size
951 @project.issue_categories.each do |issue_category|
952 @project.issue_categories.each do |issue_category|
952 assert !@source_project.issue_categories.include?(issue_category)
953 assert !@source_project.issue_categories.include?(issue_category)
953 end
954 end
954 end
955 end
955
956
956 should "copy boards" do
957 should "copy boards" do
957 assert @project.copy(@source_project)
958 assert @project.copy(@source_project)
958
959
959 assert_equal 1, @project.boards.size
960 assert_equal 1, @project.boards.size
960 @project.boards.each do |board|
961 @project.boards.each do |board|
961 assert !@source_project.boards.include?(board)
962 assert !@source_project.boards.include?(board)
962 end
963 end
963 end
964 end
964
965
965 should "change the new issues to use the copied issue categories" do
966 should "change the new issues to use the copied issue categories" do
966 issue = Issue.find(4)
967 issue = Issue.find(4)
967 issue.update_attribute(:category_id, 3)
968 issue.update_attribute(:category_id, 3)
968
969
969 assert @project.copy(@source_project)
970 assert @project.copy(@source_project)
970
971
971 @project.issues.each do |issue|
972 @project.issues.each do |issue|
972 assert issue.category
973 assert issue.category
973 assert_equal "Stock management", issue.category.name # Same name
974 assert_equal "Stock management", issue.category.name # Same name
974 assert_not_equal IssueCategory.find(3), issue.category # Different record
975 assert_not_equal IssueCategory.find(3), issue.category # Different record
975 end
976 end
976 end
977 end
977
978
978 should "limit copy with :only option" do
979 should "limit copy with :only option" do
979 assert @project.members.empty?
980 assert @project.members.empty?
980 assert @project.issue_categories.empty?
981 assert @project.issue_categories.empty?
981 assert @source_project.issues.any?
982 assert @source_project.issues.any?
982
983
983 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
984 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
984
985
985 assert @project.members.any?
986 assert @project.members.any?
986 assert @project.issue_categories.any?
987 assert @project.issue_categories.any?
987 assert @project.issues.empty?
988 assert @project.issues.empty?
988 end
989 end
989
990
990 end
991 end
991
992
992 context "#start_date" do
993 context "#start_date" do
993 setup do
994 setup do
994 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
995 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
995 @project = Project.generate!(:identifier => 'test0')
996 @project = Project.generate!(:identifier => 'test0')
996 @project.trackers << Tracker.generate!
997 @project.trackers << Tracker.generate!
997 end
998 end
998
999
999 should "be nil if there are no issues on the project" do
1000 should "be nil if there are no issues on the project" do
1000 assert_nil @project.start_date
1001 assert_nil @project.start_date
1001 end
1002 end
1002
1003
1003 should "be tested when issues have no start date"
1004 should "be tested when issues have no start date"
1004
1005
1005 should "be the earliest start date of it's issues" do
1006 should "be the earliest start date of it's issues" do
1006 early = 7.days.ago.to_date
1007 early = 7.days.ago.to_date
1007 Issue.generate_for_project!(@project, :start_date => Date.today)
1008 Issue.generate_for_project!(@project, :start_date => Date.today)
1008 Issue.generate_for_project!(@project, :start_date => early)
1009 Issue.generate_for_project!(@project, :start_date => early)
1009
1010
1010 assert_equal early, @project.start_date
1011 assert_equal early, @project.start_date
1011 end
1012 end
1012
1013
1013 end
1014 end
1014
1015
1015 context "#due_date" do
1016 context "#due_date" do
1016 setup do
1017 setup do
1017 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1018 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1018 @project = Project.generate!(:identifier => 'test0')
1019 @project = Project.generate!(:identifier => 'test0')
1019 @project.trackers << Tracker.generate!
1020 @project.trackers << Tracker.generate!
1020 end
1021 end
1021
1022
1022 should "be nil if there are no issues on the project" do
1023 should "be nil if there are no issues on the project" do
1023 assert_nil @project.due_date
1024 assert_nil @project.due_date
1024 end
1025 end
1025
1026
1026 should "be tested when issues have no due date"
1027 should "be tested when issues have no due date"
1027
1028
1028 should "be the latest due date of it's issues" do
1029 should "be the latest due date of it's issues" do
1029 future = 7.days.from_now.to_date
1030 future = 7.days.from_now.to_date
1030 Issue.generate_for_project!(@project, :due_date => future)
1031 Issue.generate_for_project!(@project, :due_date => future)
1031 Issue.generate_for_project!(@project, :due_date => Date.today)
1032 Issue.generate_for_project!(@project, :due_date => Date.today)
1032
1033
1033 assert_equal future, @project.due_date
1034 assert_equal future, @project.due_date
1034 end
1035 end
1035
1036
1036 should "be the latest due date of it's versions" do
1037 should "be the latest due date of it's versions" do
1037 future = 7.days.from_now.to_date
1038 future = 7.days.from_now.to_date
1038 @project.versions << Version.generate!(:effective_date => future)
1039 @project.versions << Version.generate!(:effective_date => future)
1039 @project.versions << Version.generate!(:effective_date => Date.today)
1040 @project.versions << Version.generate!(:effective_date => Date.today)
1040
1041
1041
1042
1042 assert_equal future, @project.due_date
1043 assert_equal future, @project.due_date
1043
1044
1044 end
1045 end
1045
1046
1046 should "pick the latest date from it's issues and versions" do
1047 should "pick the latest date from it's issues and versions" do
1047 future = 7.days.from_now.to_date
1048 future = 7.days.from_now.to_date
1048 far_future = 14.days.from_now.to_date
1049 far_future = 14.days.from_now.to_date
1049 Issue.generate_for_project!(@project, :due_date => far_future)
1050 Issue.generate_for_project!(@project, :due_date => far_future)
1050 @project.versions << Version.generate!(:effective_date => future)
1051 @project.versions << Version.generate!(:effective_date => future)
1051
1052
1052 assert_equal far_future, @project.due_date
1053 assert_equal far_future, @project.due_date
1053 end
1054 end
1054
1055
1055 end
1056 end
1056
1057
1057 context "Project#completed_percent" do
1058 context "Project#completed_percent" do
1058 setup do
1059 setup do
1059 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1060 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1060 @project = Project.generate!(:identifier => 'test0')
1061 @project = Project.generate!(:identifier => 'test0')
1061 @project.trackers << Tracker.generate!
1062 @project.trackers << Tracker.generate!
1062 end
1063 end
1063
1064
1064 context "no versions" do
1065 context "no versions" do
1065 should "be 100" do
1066 should "be 100" do
1066 assert_equal 100, @project.completed_percent
1067 assert_equal 100, @project.completed_percent
1067 end
1068 end
1068 end
1069 end
1069
1070
1070 context "with versions" do
1071 context "with versions" do
1071 should "return 0 if the versions have no issues" do
1072 should "return 0 if the versions have no issues" do
1072 Version.generate!(:project => @project)
1073 Version.generate!(:project => @project)
1073 Version.generate!(:project => @project)
1074 Version.generate!(:project => @project)
1074
1075
1075 assert_equal 0, @project.completed_percent
1076 assert_equal 0, @project.completed_percent
1076 end
1077 end
1077
1078
1078 should "return 100 if the version has only closed issues" do
1079 should "return 100 if the version has only closed issues" do
1079 v1 = Version.generate!(:project => @project)
1080 v1 = Version.generate!(:project => @project)
1080 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1081 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1081 v2 = Version.generate!(:project => @project)
1082 v2 = Version.generate!(:project => @project)
1082 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1083 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1083
1084
1084 assert_equal 100, @project.completed_percent
1085 assert_equal 100, @project.completed_percent
1085 end
1086 end
1086
1087
1087 should "return the averaged completed percent of the versions (not weighted)" do
1088 should "return the averaged completed percent of the versions (not weighted)" do
1088 v1 = Version.generate!(:project => @project)
1089 v1 = Version.generate!(:project => @project)
1089 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1090 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1090 v2 = Version.generate!(:project => @project)
1091 v2 = Version.generate!(:project => @project)
1091 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1092 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1092
1093
1093 assert_equal 50, @project.completed_percent
1094 assert_equal 50, @project.completed_percent
1094 end
1095 end
1095
1096
1096 end
1097 end
1097 end
1098 end
1098
1099
1099 context "#notified_users" do
1100 context "#notified_users" do
1100 setup do
1101 setup do
1101 @project = Project.generate!
1102 @project = Project.generate!
1102 @role = Role.generate!
1103 @role = Role.generate!
1103
1104
1104 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
1105 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
1105 Member.generate!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
1106 Member.generate!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
1106
1107
1107 @all_events_user = User.generate!(:mail_notification => 'all')
1108 @all_events_user = User.generate!(:mail_notification => 'all')
1108 Member.generate!(:project => @project, :roles => [@role], :principal => @all_events_user)
1109 Member.generate!(:project => @project, :roles => [@role], :principal => @all_events_user)
1109
1110
1110 @no_events_user = User.generate!(:mail_notification => 'none')
1111 @no_events_user = User.generate!(:mail_notification => 'none')
1111 Member.generate!(:project => @project, :roles => [@role], :principal => @no_events_user)
1112 Member.generate!(:project => @project, :roles => [@role], :principal => @no_events_user)
1112
1113
1113 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
1114 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
1114 Member.generate!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
1115 Member.generate!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
1115
1116
1116 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
1117 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
1117 Member.generate!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
1118 Member.generate!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
1118
1119
1119 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
1120 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
1120 Member.generate!(:project => @project, :roles => [@role], :principal => @only_owned_user)
1121 Member.generate!(:project => @project, :roles => [@role], :principal => @only_owned_user)
1121 end
1122 end
1122
1123
1123 should "include members with a mail notification" do
1124 should "include members with a mail notification" do
1124 assert @project.notified_users.include?(@user_with_membership_notification)
1125 assert @project.notified_users.include?(@user_with_membership_notification)
1125 end
1126 end
1126
1127
1127 should "include users with the 'all' notification option" do
1128 should "include users with the 'all' notification option" do
1128 assert @project.notified_users.include?(@all_events_user)
1129 assert @project.notified_users.include?(@all_events_user)
1129 end
1130 end
1130
1131
1131 should "not include users with the 'none' notification option" do
1132 should "not include users with the 'none' notification option" do
1132 assert !@project.notified_users.include?(@no_events_user)
1133 assert !@project.notified_users.include?(@no_events_user)
1133 end
1134 end
1134
1135
1135 should "not include users with the 'only_my_events' notification option" do
1136 should "not include users with the 'only_my_events' notification option" do
1136 assert !@project.notified_users.include?(@only_my_events_user)
1137 assert !@project.notified_users.include?(@only_my_events_user)
1137 end
1138 end
1138
1139
1139 should "not include users with the 'only_assigned' notification option" do
1140 should "not include users with the 'only_assigned' notification option" do
1140 assert !@project.notified_users.include?(@only_assigned_user)
1141 assert !@project.notified_users.include?(@only_assigned_user)
1141 end
1142 end
1142
1143
1143 should "not include users with the 'only_owner' notification option" do
1144 should "not include users with the 'only_owner' notification option" do
1144 assert !@project.notified_users.include?(@only_owned_user)
1145 assert !@project.notified_users.include?(@only_owned_user)
1145 end
1146 end
1146 end
1147 end
1147
1148
1148 end
1149 end
General Comments 0
You need to be logged in to leave comments. Login now