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