Package trac :: Package tests :: Module attachment

Source Code for Module trac.tests.attachment

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2005-2020 Edgewall Software 
  4  # All rights reserved. 
  5  # 
  6  # This software is licensed as described in the file COPYING, which 
  7  # you should have received as part of this distribution. The terms 
  8  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
  9  # 
 10  # This software consists of voluntary contributions made by many 
 11  # individuals. For the exact contribution history, see the revision 
 12  # history and logs, available at https://trac.edgewall.org/log/. 
 13   
 14  import os 
 15  import tempfile 
 16  import unittest 
 17  import zipfile 
 18  from datetime import datetime 
 19  from StringIO import StringIO 
 20   
 21  from trac.attachment import Attachment, AttachmentModule 
 22  from trac.core import Component, implements, TracError 
 23  from trac.perm import IPermissionPolicy, PermissionCache 
 24  from trac.resource import IResourceManager, Resource, resource_exists 
 25  from trac.test import EnvironmentStub, MockRequest 
 26  from trac.util.datefmt import utc, to_utimestamp 
 27  from trac.web.api import HTTPBadRequest, RequestDone 
 28   
 29   
 30  hashes = { 
 31      '42': '92cfceb39d57d914ed8b14d0e37643de0797ae56', 
 32      'Foo.Mp3': '95797b6eb253337ff2c54e0881e2b747ec394f51', 
 33      'SomePage': 'd7e80bae461ca8568e794792f5520b603f540e06', 
 34      'Teh bar.jpg': 'ed9102c4aa099e92baf1073f824d21c5e4be5944', 
 35      'Teh foo.txt': 'ab97ba98d98fcf72b92e33a66b07077010171f70', 
 36      'bar.7z': '6c9600ad4d59ac864e6f0d2030c1fc76b4b406cb', 
 37      'bar.jpg': 'ae0faa593abf2b6f8871f6f32fe5b28d1c6572be', 
 38      'foo.$$$': 'eefc6aa745dbe129e8067a4a57637883edd83a8a', 
 39      'foo.2.txt': 'a8fcfcc2ef4e400ee09ae53c1aabd7f5a5fda0c7', 
 40      'foo.txt': '9206ac42b532ef8e983470c251f4e1a365fd636c', 
 41      u'bar.aäc': '70d0e3b813fdc756602d82748719a3ceb85cbf29', 
 42      u'ÜberSicht': 'a16c6837f6d3d2cc3addd68976db1c55deb694c8', 
 43  } 
 44   
 45   
46 -class TicketOnlyViewsTicket(Component):
47 implements(IPermissionPolicy) 48
49 - def check_permission(self, action, username, resource, perm):
50 if action.startswith('TICKET_'): 51 return resource.realm == 'ticket' 52 else: 53 return None
54 55
56 -class AttachmentTestCase(unittest.TestCase):
57
58 - def setUp(self):
59 self.env = EnvironmentStub() 60 self.env.path = tempfile.mkdtemp(prefix='trac-tempenv-') 61 self.attachments_dir = os.path.join(self.env.path, 'files', 62 'attachments') 63 self.env.enable_component(TicketOnlyViewsTicket) 64 self.env.config.set('trac', 'permission_policies', 65 'TicketOnlyViewsTicket, LegacyAttachmentPolicy') 66 self.env.config.set('attachment', 'max_size', 512) 67 68 self.perm = PermissionCache(self.env) 69 self.datetime = datetime(2001, 1, 1, 1, 1, 1, 0, utc) 70 with self.env.db_transaction as db: 71 db("INSERT INTO wiki (name,version) VALUES ('WikiStart',1)") 72 db("INSERT INTO wiki (name,version) VALUES ('SomePage',1)") 73 db("INSERT INTO ticket (id) VALUES (42)") 74 db("INSERT INTO ticket (id) VALUES (43)") 75 db("INSERT INTO attachment VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", 76 ('ticket', '43', 'foo.txt', 8, to_utimestamp(self.datetime), 77 'A comment', 'joe', '::1'))
78
79 - def tearDown(self):
81
82 - def test_new_attachment(self):
83 attachment = Attachment(self.env, 'ticket', 42) 84 self.assertEqual(None, attachment.filename) 85 self.assertEqual(None, attachment.description) 86 self.assertEqual(None, attachment.size) 87 self.assertEqual(None, attachment.date) 88 self.assertEqual(None, attachment.author) 89 self.assertEqual(None, attachment.ipnr) 90 self.assertEqual('<Attachment None>', repr(attachment))
91
92 - def test_existing_attachment(self):
93 attachment = Attachment(self.env, 'ticket', 43, 'foo.txt') 94 self.assertEqual('foo.txt', attachment.filename) 95 self.assertEqual('A comment', attachment.description) 96 self.assertEqual(8, attachment.size) 97 self.assertEqual(self.datetime, attachment.date) 98 self.assertEqual('joe', attachment.author) 99 self.assertEqual('::1', attachment.ipnr) 100 self.assertEqual("<Attachment u'foo.txt'>", repr(attachment))
101
103 resource = Resource('ticket', 43).child('attachment', 'foo.txt') 104 attachment = Attachment(self.env, resource) 105 self.assertEqual('foo.txt', attachment.filename) 106 self.assertEqual('A comment', attachment.description) 107 self.assertEqual(8, attachment.size) 108 self.assertEqual(self.datetime, attachment.date) 109 self.assertEqual('joe', attachment.author) 110 self.assertEqual('::1', attachment.ipnr) 111 self.assertEqual("<Attachment u'foo.txt'>", repr(attachment))
112
113 - def test_get_path(self):
114 attachment = Attachment(self.env, 'ticket', 42) 115 attachment.filename = 'foo.txt' 116 self.assertEqual(os.path.join(self.attachments_dir, 'ticket', 117 hashes['42'][0:3], hashes['42'], 118 hashes['foo.txt'] + '.txt'), 119 attachment.path) 120 attachment = Attachment(self.env, 'wiki', 'SomePage') 121 attachment.filename = 'bar.jpg' 122 self.assertEqual(os.path.join(self.attachments_dir, 'wiki', 123 hashes['SomePage'][0:3], 124 hashes['SomePage'], 125 hashes['bar.jpg'] + '.jpg'), 126 attachment.path)
127
128 - def test_path_extension(self):
129 attachment = Attachment(self.env, 'ticket', 42) 130 attachment.filename = 'Foo.Mp3' 131 self.assertEqual(os.path.join(self.attachments_dir, 'ticket', 132 hashes['42'][0:3], hashes['42'], 133 hashes['Foo.Mp3'] + '.Mp3'), 134 attachment.path) 135 attachment = Attachment(self.env, 'wiki', 'SomePage') 136 attachment.filename = 'bar.7z' 137 self.assertEqual(os.path.join(self.attachments_dir, 'wiki', 138 hashes['SomePage'][0:3], 139 hashes['SomePage'], 140 hashes['bar.7z'] + '.7z'), 141 attachment.path) 142 attachment = Attachment(self.env, 'ticket', 42) 143 attachment.filename = 'foo.$$$' 144 self.assertEqual(os.path.join(self.attachments_dir, 'ticket', 145 hashes['42'][0:3], hashes['42'], 146 hashes['foo.$$$']), 147 attachment.path) 148 attachment = Attachment(self.env, 'wiki', 'SomePage') 149 attachment.filename = u'bar.aäc' 150 self.assertEqual(os.path.join(self.attachments_dir, 'wiki', 151 hashes['SomePage'][0:3], 152 hashes['SomePage'], 153 hashes[u'bar.aäc']), 154 attachment.path)
155
156 - def test_get_path_encoded(self):
157 attachment = Attachment(self.env, 'ticket', 42) 158 attachment.filename = 'Teh foo.txt' 159 self.assertEqual(os.path.join(self.attachments_dir, 'ticket', 160 hashes['42'][0:3], hashes['42'], 161 hashes['Teh foo.txt'] + '.txt'), 162 attachment.path) 163 attachment = Attachment(self.env, 'wiki', u'ÜberSicht') 164 attachment.filename = 'Teh bar.jpg' 165 self.assertEqual(os.path.join(self.attachments_dir, 'wiki', 166 hashes[u'ÜberSicht'][0:3], 167 hashes[u'ÜberSicht'], 168 hashes['Teh bar.jpg'] + '.jpg'), 169 attachment.path)
170
171 - def test_select_empty(self):
172 self.assertRaises(StopIteration, 173 Attachment.select(self.env, 'ticket', 42).next) 174 self.assertRaises(StopIteration, 175 Attachment.select(self.env, 'wiki', 'SomePage').next)
176
177 - def test_insert(self):
178 attachment = Attachment(self.env, 'ticket', 42) 179 attachment.insert('foo.txt', StringIO(''), 0, 1) 180 attachment = Attachment(self.env, 'ticket', 42) 181 attachment.insert('bar.jpg', StringIO(''), 0, 2) 182 183 attachments = Attachment.select(self.env, 'ticket', 42) 184 self.assertEqual('foo.txt', attachments.next().filename) 185 self.assertEqual('bar.jpg', attachments.next().filename) 186 self.assertRaises(StopIteration, attachments.next)
187
188 - def test_insert_unique(self):
189 attachment = Attachment(self.env, 'ticket', 42) 190 attachment.insert('foo.txt', StringIO(''), 0) 191 self.assertEqual('foo.txt', attachment.filename) 192 attachment = Attachment(self.env, 'ticket', 42) 193 attachment.insert('foo.txt', StringIO(''), 0) 194 self.assertEqual('foo.2.txt', attachment.filename) 195 self.assertEqual(os.path.join(self.attachments_dir, 'ticket', 196 hashes['42'][0:3], hashes['42'], 197 hashes['foo.2.txt'] + '.txt'), 198 attachment.path) 199 self.assertTrue(os.path.exists(attachment.path))
200
202 attachment = Attachment(self.env, '../../../../../sth/private', 42) 203 self.assertRaises(TracError, attachment.insert, 'foo.txt', 204 StringIO(''), 0)
205
206 - def test_delete(self):
207 attachment1 = Attachment(self.env, 'wiki', 'SomePage') 208 attachment1.insert('foo.txt', StringIO(''), 0) 209 attachment2 = Attachment(self.env, 'wiki', 'SomePage') 210 attachment2.insert('bar.jpg', StringIO(''), 0) 211 212 attachments = Attachment.select(self.env, 'wiki', 'SomePage') 213 self.assertEqual(2, len(list(attachments))) 214 215 attachment1.delete() 216 attachment2.delete() 217 218 self.assertFalse(os.path.exists(attachment1.path)) 219 self.assertFalse(os.path.exists(attachment2.path)) 220 221 attachments = Attachment.select(self.env, 'wiki', 'SomePage') 222 self.assertEqual(0, len(list(attachments)))
223
224 - def test_delete_file_gone(self):
225 """ 226 Verify that deleting an attachment works even if the referenced file 227 doesn't exist for some reason. 228 """ 229 attachment = Attachment(self.env, 'wiki', 'SomePage') 230 attachment.insert('foo.txt', StringIO(''), 0) 231 os.unlink(attachment.path) 232 233 attachment.delete()
234
235 - def test_reparent(self):
236 attachment1 = Attachment(self.env, 'wiki', 'SomePage') 237 attachment1.insert('foo.txt', StringIO(''), 0) 238 path1 = attachment1.path 239 attachment2 = Attachment(self.env, 'wiki', 'SomePage') 240 attachment2.insert('bar.jpg', StringIO(''), 0) 241 242 attachments = Attachment.select(self.env, 'wiki', 'SomePage') 243 self.assertEqual(2, len(list(attachments))) 244 attachments = Attachment.select(self.env, 'ticket', 123) 245 self.assertEqual(0, len(list(attachments))) 246 self.assertTrue(os.path.exists(path1) and os.path.exists(attachment2.path)) 247 248 attachment1.reparent('ticket', 123) 249 self.assertEqual('ticket', attachment1.parent_realm) 250 self.assertEqual('ticket', attachment1.resource.parent.realm) 251 self.assertEqual('123', attachment1.parent_id) 252 self.assertEqual('123', attachment1.resource.parent.id) 253 254 attachments = Attachment.select(self.env, 'wiki', 'SomePage') 255 self.assertEqual(1, len(list(attachments))) 256 attachments = Attachment.select(self.env, 'ticket', 123) 257 self.assertEqual(1, len(list(attachments))) 258 self.assertFalse(os.path.exists(path1) and os.path.exists(attachment1.path)) 259 self.assertTrue(os.path.exists(attachment2.path))
260
262 """Ensure that legacy action tests are done on parent. As 263 `ATTACHMENT_VIEW` maps to `TICKET_VIEW`, the `TICKET_VIEW` is tested 264 against the ticket's resource.""" 265 attachment = Attachment(self.env, 'ticket', 42) 266 self.assertTrue('ATTACHMENT_VIEW' in self.perm(attachment.resource))
267
268 - def test_resource_exists(self):
269 att = Attachment(self.env, 'wiki', 'WikiStart') 270 att.insert('file.txt', StringIO(''), 1) 271 self.assertTrue(resource_exists(self.env, att.resource))
272 273
274 -class AttachmentModuleTestCase(unittest.TestCase):
275
276 - def setUp(self):
277 class GenericResourceManager(Component): 278 279 implements(IResourceManager) 280 281 def get_resource_realms(self): 282 yield 'parent_realm'
283 284 def get_resource_url(self, resource, href, **kwargs): 285 pass
286 287 def get_resource_description(self, resource, format='default', 288 context=None, **kwargs): 289 pass 290 291 def resource_exists(self, resource): 292 return resource.id == 'parent_id' 293 294 self.env = EnvironmentStub(enable=(GenericResourceManager,)) 295 self.env.path = tempfile.mkdtemp(prefix='trac-tempenv-') 296
297 - def tearDown(self):
298 self.env.reset_db_and_disk()
299
300 - def test_invalid_post_request_raises_exception(self):
301 302 path_info = '/attachment/parent_realm/parent_id/attachment_id' 303 attachment = Attachment(self.env, 'parent_realm', 'parent_id') 304 attachment.insert('attachment_id', StringIO(''), 0, 1) 305 req = MockRequest(self.env, method='POST', action=None, 306 path_info=path_info) 307 module = AttachmentModule(self.env) 308 309 self.assertTrue(module.match_request(req)) 310 self.assertRaises(HTTPBadRequest, module.process_request, req)
311
312 - def test_post_request_without_attachment_raises_exception(self):
313 """TracError is raised when a POST request is submitted 314 without an attachment. 315 """ 316 path_info = '/attachment/parent_realm/parent_id' 317 req = MockRequest(self.env, path_info=path_info, method='POST', 318 args={'action': 'new'}) 319 module = AttachmentModule(self.env) 320 321 self.assertTrue(module.match_request(req)) 322 self.assertRaises(TracError, module.process_request, req)
323
324 - def test_attachment_parent_realm_raises_exception(self):
325 """TracError is raised when 'attachment' is the resource parent 326 realm. 327 """ 328 path_info = '/attachment/attachment/parent_id/attachment_id' 329 req = MockRequest(self.env, path_info=path_info) 330 module = AttachmentModule(self.env) 331 332 self.assertTrue(module.match_request(req)) 333 self.assertRaises(TracError, module.process_request, req)
334
335 - def test_resource_doesnt_exist(self):
336 """Non-existent resource returns False from resource_exists.""" 337 parent = Resource('parent_realm', 'parent_id') 338 self.assertTrue(resource_exists(self.env, parent)) 339 r = parent.child('attachment', 'file.txt') 340 self.assertFalse(resource_exists(self.env, r))
341
342 - def test_download_zip(self):
343 att = Attachment(self.env, 'parent_realm', 'parent_id') 344 att.description = 'Blah blah' 345 att.insert('foo.txt', StringIO('foo'), 3, 346 datetime(2016, 9, 23, 12, 34, 56, tzinfo=utc)) 347 att = Attachment(self.env, 'parent_realm', 'parent_id') 348 att.insert('bar.jpg', StringIO('bar'), 3, 349 datetime(2016, 12, 14, 23, 56, 30, tzinfo=utc)) 350 module = AttachmentModule(self.env) 351 req = MockRequest(self.env, args={'format': 'zip'}, 352 path_info='/attachment/parent_realm/parent_id/') 353 354 self.assertTrue(module.match_request(req)) 355 self.assertRaises(RequestDone, module.process_request, req) 356 z = zipfile.ZipFile(req.response_sent, 'r') 357 self.assertEqual(['bar.jpg', 'foo.txt'], 358 sorted(i.filename for i in z.infolist())) 359 360 zinfo = z.getinfo('foo.txt') 361 self.assertEqual('foo', z.read('foo.txt')) 362 self.assertEqual(3, zinfo.file_size) 363 self.assertEqual((2016, 9, 23, 12, 34, 56), zinfo.date_time) 364 self.assertEqual('Blah blah', zinfo.comment) 365 366 zinfo = z.getinfo('bar.jpg') 367 self.assertEqual('bar', z.read('bar.jpg')) 368 self.assertEqual(3, zinfo.file_size) 369 self.assertEqual((2016, 12, 14, 23, 56, 30), zinfo.date_time) 370 self.assertEqual('', zinfo.comment)
371 372
373 -def test_suite():
374 suite = unittest.TestSuite() 375 suite.addTest(unittest.makeSuite(AttachmentTestCase)) 376 suite.addTest(unittest.makeSuite(AttachmentModuleTestCase)) 377 return suite
378 379 if __name__ == '__main__': 380 unittest.main(defaultTest='test_suite') 381