Exemple #1
0
class TestTUS(unittest.TestCase):

    layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING

    def setUp(self):
        self.app = self.layer['app']
        self.portal = self.layer['portal']
        login(self.portal, SITE_OWNER_NAME)

        self.folder = api.content.create(container=self.portal,
                                         type='Folder',
                                         id='testfolder',
                                         title='Testfolder')
        self.upload_url = '{}/@tus-upload'.format(self.folder.absolute_url())
        transaction.commit()

        self.api_session = RelativeSession(self.portal.absolute_url())
        self.api_session.headers.update({'Accept': 'application/json'})
        self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)

    def get_tus_uid_from_url(self, url):
        uid = url.rsplit('/', 1)[-1]
        assert len(uid) == 32
        return uid

    def get_tus_upload_instance(self, uid):
        return TUSUpload(uid)

    def test_tus_option_headers(self):
        response = self.api_session.options(self.upload_url)
        headers = response.headers
        self.assertEqual(response.status_code, 204)
        self.assertEqual(headers['Tus-Version'], '1.0.0')
        self.assertEqual(headers['Tus-Extension'], 'creation,expiration')
        self.assertEqual(headers['Tus-Resumable'], '1.0.0')

    def test_tus_post_without_version_header_returns_412(self):
        response = self.api_session.post(self.upload_url)
        self.assertEqual(412, response.status_code)

    def test_tus_post_with_wrong_version_header_returns_412(self):
        response = self.api_session.post(self.upload_url,
                                         headers={'Tus-Resumable': '0.2.2'})
        self.assertEqual(412, response.status_code)

    def test_tus_post_initialization_requires_header_length(self):
        response = self.api_session.post(self.upload_url,
                                         headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(response.json()['error']['type'], 'Bad Request')
        self.assertEqual(response.json()['error']['message'],
                         'Missing or invalid Upload-Length header')
        self.assertEqual(response.status_code, 400)

    def test_tus_post_initialization(self):
        response = self.api_session.post(self.upload_url,
                                         headers={
                                             'Tus-Resumable': '1.0.0',
                                             'Upload-Length':
                                             str(UPLOAD_LENGTH),
                                         })
        self.assertEqual(response.status_code, 201)
        location = response.headers['Location']
        url_base, uid = location.rsplit('/', 1)
        self.assertEqual(url_base, self.upload_url)
        self.assertEqual(len(uid), 32)
        upload = TUSUpload(uid)
        stored_metadata = upload.metadata()
        self.assertEqual(stored_metadata, {u'length': 8, u'mode': u'create'})
        upload.cleanup()

    def test_tus_post_initialization_with_metadata(self):
        metadata = 'filename {},content-type {}'.format(
            b64encode(UPLOAD_FILENAME), b64encode(UPLOAD_MIMETYPE))
        response = self.api_session.post(self.upload_url,
                                         headers={
                                             'Tus-Resumable': '1.0.0',
                                             'Upload-Length':
                                             str(UPLOAD_LENGTH),
                                             'Upload-Metadata': metadata
                                         })
        self.assertEqual(response.status_code, 201)
        uid = self.get_tus_uid_from_url(response.headers['Location'])
        upload = TUSUpload(uid)
        stored_metadata = upload.metadata()
        self.assertEqual(
            stored_metadata, {
                u'content-type': u'text/plain',
                u'filename': u'test.txt',
                u'length': 8,
                u'mode': u'create'
            })
        upload.cleanup()

    def test_tus_post_replace(self):
        self.file = api.content.create(container=self.portal,
                                       type='File',
                                       id='testfile',
                                       title='Testfile')
        transaction.commit()
        response = self.api_session.post(
            '{}/@tus-replace'.format(self.file.absolute_url()),
            headers={
                'Tus-Resumable': '1.0.0',
                'Upload-Length': str(UPLOAD_LENGTH),
            })
        self.assertEqual(response.status_code, 201)
        location = response.headers['Location']
        url_base, uid = location.rsplit('/', 1)
        upload = TUSUpload(uid)
        stored_metadata = upload.metadata()
        self.assertEqual(stored_metadata, {u'length': 8, u'mode': u'replace'})
        upload.cleanup()

    def test_tus_head_on_not_existing_resource_returns_404(self):
        response = self.api_session.head(self.upload_url + '/myuid/123',
                                         headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(404, response.status_code)
        response = self.api_session.head(self.upload_url + '/non-existing-uid',
                                         headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(404, response.status_code)
        response = self.api_session.head(self.upload_url,
                                         headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(404, response.status_code)

    def test_tus_head_with_unsupported_version_returns_412(self):
        tus = TUSUpload('myuid', {'length': 2048})
        response = self.api_session.head(self.upload_url + '/myuid',
                                         headers={'Tus-Resumable': '0.2.2'})
        self.assertEqual(412, response.status_code)
        tus.cleanup()

    def test_tus_head_response_includes_required_headers(self):
        tus = TUSUpload('myuid', {'length': 2048})
        response = self.api_session.head(self.upload_url + '/myuid',
                                         headers={'Tus-Resumable': '1.0.0'})
        self.assertIn('Upload-Length', response.headers)
        self.assertEqual('2048', response.headers['Upload-Length'])
        self.assertIn('Upload-Offset', response.headers)
        self.assertIn('Tus-Resumable', response.headers)
        self.assertIn('Cache-Control', response.headers)
        tus.cleanup()

    def test_head_in_create_mode_without_add_permission_raises_401(self):
        self.folder.manage_permission('Add portal content', [], 0)
        transaction.commit()
        tus = TUSUpload('myuid', {'mode': 'create', 'length': 12})
        response = self.api_session.head(self.upload_url + '/myuid',
                                         headers={
                                             'Tus-Resumable': '1.0.0',
                                             'Upload-Offset': '0'
                                         })
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_head_in_replace_mode_without_modify_permission_raises_401(self):
        self.folder.manage_permission('Modify portal content', [], 0)
        transaction.commit()
        tus = TUSUpload('myuid', {'mode': 'replace', 'length': 12})
        response = self.api_session.head(self.upload_url + '/myuid',
                                         headers={
                                             'Tus-Resumable': '1.0.0',
                                             'Upload-Offset': '0'
                                         })
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_tus_patch_on_not_existing_resource_returns_404(self):
        response = self.api_session.patch(self.upload_url + '/myuid/123',
                                          headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(404, response.status_code)
        response = self.api_session.patch(self.upload_url + '/myuid',
                                          headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(404, response.status_code)
        response = self.api_session.patch(self.upload_url,
                                          headers={'Tus-Resumable': '1.0.0'})
        self.assertEqual(404, response.status_code)

    def test_tus_patch_with_unsupported_version_returns_412(self):
        tus = TUSUpload('myuid', {'length': 2048})
        response = self.api_session.patch(self.upload_url + '/myuid',
                                          headers={'Tus-Resumable': '0.2.2'})
        self.assertEqual(412, response.status_code)
        tus.cleanup()

    def test_tus_patch_with_unsupported_content_type_returns_400(self):
        tus = TUSUpload('myuid', {'length': 2048})
        response = self.api_session.patch(self.upload_url + '/myuid',
                                          headers={
                                              'Tus-Resumable': '1.0.0',
                                              'Content-Type':
                                              'application/json'
                                          })
        self.assertEqual(400, response.status_code)
        tus.cleanup()

    def test_tus_patch_with_invalid_offset_returns_400(self):
        tus = TUSUpload('myuid', {'length': 2048})
        response = self.api_session.patch(self.upload_url + '/myuid',
                                          headers={
                                              'Tus-Resumable':
                                              '1.0.0',
                                              'Content-Type':
                                              'application/offset+octet-stream'
                                          })
        self.assertEqual(400, response.status_code)
        tus.cleanup()

    def test_tus_patch_unfinished_upload_returns_expires_header(self):
        tus = TUSUpload('myuid', {'length': 2048})
        response = self.api_session.patch(
            self.upload_url + '/myuid',
            headers={
                'Tus-Resumable': '1.0.0',
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0'
            },
            data=StringIO('abcdefghijkl'))
        self.assertEqual(204, response.status_code)
        self.assertIn('Upload-Expires', response.headers)
        tus.cleanup()

    def test_tus_patch_non_primary_field(self):
        tus = TUSUpload(
            'myuid', {
                '@type': 'DXTestDocument',
                'length': 12,
                'fieldname': 'test_namedblobfile_field'
            })
        response = self.api_session.patch(
            self.upload_url + '/myuid',
            headers={
                'Tus-Resumable': '1.0.0',
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0'
            },
            data=StringIO('abcdefghijkl'))

        self.assertEqual(204, response.status_code)
        transaction.commit()
        self.assertEqual(1, len(self.folder.objectIds()))
        id_ = self.folder.objectIds()[0]
        self.assertEqual('abcdefghijkl',
                         self.folder[id_].test_namedblobfile_field.data)
        tus.cleanup()

    def test_patch_in_create_mode_without_add_permission_raises_401(self):
        self.folder.manage_permission('Add portal content', [], 0)
        transaction.commit()
        tus = TUSUpload('myuid', {'mode': 'create', 'length': 12})
        response = self.api_session.patch(
            self.upload_url + '/myuid',
            headers={
                'Tus-Resumable': '1.0.0',
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0'
            },
            data=StringIO('abcdefghijkl'))
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_patch_in_replace_mode_without_modify_permission_raises_401(self):
        self.folder.manage_permission('Modify portal content', [], 0)
        transaction.commit()
        tus = TUSUpload('myuid', {'mode': 'replace', 'length': 12})
        response = self.api_session.patch(
            self.upload_url + '/myuid',
            headers={
                'Tus-Resumable': '1.0.0',
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0'
            },
            data=StringIO('abcdefghijkl'))
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_tus_can_upload_pdf_file(self):
        # initialize the upload with POST
        pdf_file_path = os.path.join(os.path.dirname(__file__),
                                     UPLOAD_PDF_FILENAME)
        pdf_file_size = os.path.getsize(pdf_file_path)
        metadata = 'filename {},content-type {}'.format(
            b64encode(UPLOAD_PDF_FILENAME), b64encode(UPLOAD_PDF_MIMETYPE))
        response = self.api_session.post(self.upload_url,
                                         headers={
                                             'Tus-Resumable': '1.0.0',
                                             'Upload-Length':
                                             str(pdf_file_size),
                                             'Upload-Metadata': metadata
                                         })
        self.assertEqual(response.status_code, 201)
        location = response.headers['Location']

        # upload the data with PATCH
        pdf_file = open(pdf_file_path, 'rb')
        response = self.api_session.patch(
            location,
            headers={
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0',
                'Tus-Resumable': '1.0.0'
            },
            data=pdf_file)
        self.assertEqual(response.status_code, 204)

        transaction.commit()
        self.assertEqual([UPLOAD_PDF_FILENAME], self.folder.contentIds())

    def test_tus_can_upload_text_file(self):
        # initialize the upload with POST
        metadata = 'filename {},content-type {}'.format(
            b64encode(UPLOAD_FILENAME), b64encode(UPLOAD_MIMETYPE))
        response = self.api_session.post(self.upload_url,
                                         headers={
                                             'Tus-Resumable': '1.0.0',
                                             'Upload-Length':
                                             str(UPLOAD_LENGTH),
                                             'Upload-Metadata': metadata
                                         })
        self.assertEqual(response.status_code, 201)
        location = response.headers['Location']

        # upload the data with PATCH
        response = self.api_session.patch(
            location,
            headers={
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0',
                'Tus-Resumable': '1.0.0'
            },
            data=StringIO(UPLOAD_DATA))
        self.assertEqual(response.status_code, 204)

    def test_tus_can_replace_pdf_file(self):
        # Create a test file
        self.file = api.content.create(container=self.portal,
                                       type='File',
                                       id='testfile',
                                       title='Testfile')
        transaction.commit()
        # initialize the upload with POST
        pdf_file_path = os.path.join(os.path.dirname(__file__),
                                     UPLOAD_PDF_FILENAME)
        pdf_file_size = os.path.getsize(pdf_file_path)
        metadata = 'filename {},content-type {}'.format(
            b64encode(UPLOAD_PDF_FILENAME), b64encode(UPLOAD_PDF_MIMETYPE))
        response = self.api_session.post(
            '{}/@tus-replace'.format(self.file.absolute_url()),
            headers={
                'Tus-Resumable': '1.0.0',
                'Upload-Length': str(pdf_file_size),
                'Upload-Metadata': metadata
            })
        self.assertEqual(response.status_code, 201)
        location = response.headers['Location']

        # upload the data with PATCH
        pdf_file = open(pdf_file_path, 'rb')
        response = self.api_session.patch(
            location,
            headers={
                'Content-Type': 'application/offset+octet-stream',
                'Upload-Offset': '0',
                'Tus-Resumable': '1.0.0'
            },
            data=pdf_file)
        self.assertEqual(response.status_code, 204)

        transaction.commit()
        self.assertEqual(UPLOAD_PDF_FILENAME, self.file.file.filename)
        self.assertEqual(pdf_file_size, self.file.file.size)

    def tearDown(self):
        client_home = os.environ.get('CLIENT_HOME')
        tmp_dir = os.path.join(client_home, 'tus-uploads')
        if os.path.isdir(tmp_dir):
            shutil.rmtree(tmp_dir)
Exemple #2
0
class TestTUS(unittest.TestCase):

    layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING

    def setUp(self):
        self.app = self.layer["app"]
        self.portal = self.layer["portal"]
        login(self.portal, SITE_OWNER_NAME)

        self.folder = api.content.create(container=self.portal,
                                         type="Folder",
                                         id="testfolder",
                                         title="Testfolder")
        self.upload_url = f"{self.folder.absolute_url()}/@tus-upload"
        transaction.commit()

        self.api_session = RelativeSession(self.portal.absolute_url(),
                                           test=self)
        self.api_session.headers.update({"Accept": "application/json"})
        self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)

    def get_tus_uid_from_url(self, url):
        uid = url.rsplit("/", 1)[-1]
        assert len(uid) == 32
        return uid

    def get_tus_upload_instance(self, uid):
        return TUSUpload(uid)

    def test_tus_option_headers(self):
        response = self.api_session.options(self.upload_url)
        headers = response.headers
        self.assertEqual(response.status_code, 204)
        self.assertEqual(headers["Tus-Version"], "1.0.0")
        self.assertEqual(headers["Tus-Extension"], "creation,expiration")
        self.assertEqual(headers["Tus-Resumable"], "1.0.0")

    def test_tus_post_without_version_header_returns_412(self):
        response = self.api_session.post(self.upload_url)
        self.assertEqual(412, response.status_code)

    def test_tus_post_with_wrong_version_header_returns_412(self):
        response = self.api_session.post(self.upload_url,
                                         headers={"Tus-Resumable": "0.2.2"})
        self.assertEqual(412, response.status_code)

    def test_tus_post_initialization_requires_header_length(self):
        response = self.api_session.post(self.upload_url,
                                         headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(response.json()["error"]["type"], "Bad Request")
        self.assertEqual(
            response.json()["error"]["message"],
            "Missing or invalid Upload-Length header",
        )
        self.assertEqual(response.status_code, 400)

    def test_tus_post_initialization(self):
        response = self.api_session.post(
            self.upload_url,
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(UPLOAD_LENGTH)
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]
        url_base, uid = location.rsplit("/", 1)
        self.assertEqual(url_base, self.upload_url)
        self.assertEqual(len(uid), 32)
        upload = TUSUpload(uid)
        stored_metadata = upload.metadata()
        self.assertEqual(stored_metadata, {"length": 8, "mode": "create"})
        upload.cleanup()

    def test_tus_post_initialization_with_metadata(self):
        metadata = _prepare_metadata(UPLOAD_FILENAME, UPLOAD_MIMETYPE)
        response = self.api_session.post(
            self.upload_url,
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(UPLOAD_LENGTH),
                "Upload-Metadata": metadata,
            },
        )
        self.assertEqual(response.status_code, 201)
        uid = self.get_tus_uid_from_url(response.headers["Location"])
        upload = TUSUpload(uid)
        stored_metadata = upload.metadata()
        self.assertEqual(
            stored_metadata,
            {
                "content-type": "text/plain",
                "filename": "test.txt",
                "length": 8,
                "mode": "create",
            },
        )
        upload.cleanup()

    def test_tus_post_replace(self):
        self.file = api.content.create(container=self.portal,
                                       type="File",
                                       id="testfile",
                                       title="Testfile")
        transaction.commit()
        response = self.api_session.post(
            f"{self.file.absolute_url()}/@tus-replace",
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(UPLOAD_LENGTH)
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]
        url_base, uid = location.rsplit("/", 1)
        upload = TUSUpload(uid)
        stored_metadata = upload.metadata()
        self.assertEqual(stored_metadata, {"length": 8, "mode": "replace"})
        upload.cleanup()

    def test_tus_head_on_not_existing_resource_returns_404(self):
        response = self.api_session.head(self.upload_url + "/myuid/123",
                                         headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(404, response.status_code)
        response = self.api_session.head(self.upload_url + "/non-existing-uid",
                                         headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(404, response.status_code)
        response = self.api_session.head(self.upload_url,
                                         headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(404, response.status_code)

    def test_tus_head_with_unsupported_version_returns_412(self):
        tus = TUSUpload("myuid", {"length": 2048})
        response = self.api_session.head(self.upload_url + "/myuid",
                                         headers={"Tus-Resumable": "0.2.2"})
        self.assertEqual(412, response.status_code)
        tus.cleanup()

    def test_tus_head_response_includes_required_headers(self):
        tus = TUSUpload("myuid", {"length": 2048})
        response = self.api_session.head(self.upload_url + "/myuid",
                                         headers={"Tus-Resumable": "1.0.0"})
        self.assertIn("Upload-Length", response.headers)
        self.assertEqual("2048", response.headers["Upload-Length"])
        self.assertIn("Upload-Offset", response.headers)
        self.assertIn("Tus-Resumable", response.headers)
        self.assertIn("Cache-Control", response.headers)
        tus.cleanup()

    def test_head_in_create_mode_without_add_permission_raises_401(self):
        self.folder.manage_permission("Add portal content", [], 0)
        transaction.commit()
        tus = TUSUpload("myuid", {"mode": "create", "length": 12})
        response = self.api_session.head(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Offset": "0"
            },
        )
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_head_in_replace_mode_without_modify_permission_raises_401(self):
        self.folder.manage_permission("Modify portal content", [], 0)
        transaction.commit()
        tus = TUSUpload("myuid", {"mode": "replace", "length": 12})
        response = self.api_session.head(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Offset": "0"
            },
        )
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_tus_patch_on_not_existing_resource_returns_404(self):
        response = self.api_session.patch(self.upload_url + "/myuid/123",
                                          headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(404, response.status_code)
        response = self.api_session.patch(self.upload_url + "/myuid",
                                          headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(404, response.status_code)
        response = self.api_session.patch(self.upload_url,
                                          headers={"Tus-Resumable": "1.0.0"})
        self.assertEqual(404, response.status_code)

    def test_tus_patch_with_unsupported_version_returns_412(self):
        tus = TUSUpload("myuid", {"length": 2048})
        response = self.api_session.patch(self.upload_url + "/myuid",
                                          headers={"Tus-Resumable": "0.2.2"})
        self.assertEqual(412, response.status_code)
        tus.cleanup()

    def test_tus_patch_with_unsupported_content_type_returns_400(self):
        tus = TUSUpload("myuid", {"length": 2048})
        response = self.api_session.patch(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Content-Type": "application/json"
            },
        )
        self.assertEqual(400, response.status_code)
        tus.cleanup()

    def test_tus_patch_with_invalid_offset_returns_400(self):
        tus = TUSUpload("myuid", {"length": 2048})
        response = self.api_session.patch(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Content-Type": "application/offset+octet-stream",
            },
        )
        self.assertEqual(400, response.status_code)
        tus.cleanup()

    def test_tus_patch_unfinished_upload_returns_expires_header(self):
        tus = TUSUpload("myuid", {"length": 2048})
        response = self.api_session.patch(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Content-Type": "application/offset+octet-stream",
                "Upload-Offset": "0",
            },
            data=BytesIO(b"abcdefghijkl"),
        )
        self.assertEqual(204, response.status_code)
        self.assertIn("Upload-Expires", response.headers)
        tus.cleanup()

    def test_tus_patch_non_primary_field(self):
        tus = TUSUpload(
            "myuid",
            {
                "@type": "DXTestDocument",
                "length": 12,
                "fieldname": "test_namedblobfile_field",
            },
        )
        response = self.api_session.patch(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Content-Type": "application/offset+octet-stream",
                "Upload-Offset": "0",
            },
            data=BytesIO(b"abcdefghijkl"),
        )

        self.assertEqual(204, response.status_code)
        transaction.commit()
        self.assertEqual(1, len(self.folder.objectIds()))
        id_ = self.folder.objectIds()[0]
        self.assertEqual(b"abcdefghijkl",
                         self.folder[id_].test_namedblobfile_field.data)
        tus.cleanup()

    def test_patch_in_create_mode_without_add_permission_raises_401(self):
        self.folder.manage_permission("Add portal content", [], 0)
        transaction.commit()
        tus = TUSUpload("myuid", {"mode": "create", "length": 12})
        response = self.api_session.patch(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Content-Type": "application/offset+octet-stream",
                "Upload-Offset": "0",
            },
            data=BytesIO(b"abcdefghijkl"),
        )
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_patch_in_replace_mode_without_modify_permission_raises_401(self):
        self.folder.manage_permission("Modify portal content", [], 0)
        transaction.commit()
        tus = TUSUpload("myuid", {"mode": "replace", "length": 12})
        response = self.api_session.patch(
            self.upload_url + "/myuid",
            headers={
                "Tus-Resumable": "1.0.0",
                "Content-Type": "application/offset+octet-stream",
                "Upload-Offset": "0",
            },
            data=BytesIO(b"abcdefghijkl"),
        )
        self.assertEqual(401, response.status_code)
        tus.cleanup()

    def test_tus_can_upload_pdf_file(self):
        # initialize the upload with POST
        pdf_file_path = os.path.join(os.path.dirname(__file__),
                                     UPLOAD_PDF_FILENAME)
        pdf_file_size = os.path.getsize(pdf_file_path)
        metadata = _prepare_metadata(UPLOAD_PDF_FILENAME, UPLOAD_PDF_MIMETYPE)
        response = self.api_session.post(
            self.upload_url,
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(pdf_file_size),
                "Upload-Metadata": metadata,
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]

        # upload the data with PATCH
        with open(pdf_file_path, "rb") as pdf_file:
            response = self.api_session.patch(
                location,
                headers={
                    "Content-Type": "application/offset+octet-stream",
                    "Upload-Offset": "0",
                    "Tus-Resumable": "1.0.0",
                },
                data=pdf_file,
            )
        self.assertEqual(response.status_code, 204)

        transaction.commit()
        self.assertEqual([UPLOAD_PDF_FILENAME], self.folder.contentIds())

    def test_tus_can_upload_text_file(self):
        # initialize the upload with POST
        metadata = _prepare_metadata(UPLOAD_FILENAME, UPLOAD_MIMETYPE)
        response = self.api_session.post(
            self.upload_url,
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(UPLOAD_LENGTH),
                "Upload-Metadata": metadata,
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]

        # upload the data with PATCH
        response = self.api_session.patch(
            location,
            headers={
                "Content-Type": "application/offset+octet-stream",
                "Upload-Offset": "0",
                "Tus-Resumable": "1.0.0",
            },
            data=BytesIO(UPLOAD_DATA),
        )
        self.assertEqual(response.status_code, 204)

    def test_tus_can_replace_pdf_file(self):
        # Create a test file
        self.file = api.content.create(container=self.portal,
                                       type="File",
                                       id="testfile",
                                       title="Testfile")
        transaction.commit()
        # initialize the upload with POST
        pdf_file_path = os.path.join(os.path.dirname(__file__),
                                     UPLOAD_PDF_FILENAME)
        pdf_file_size = os.path.getsize(pdf_file_path)
        metadata = _prepare_metadata(UPLOAD_PDF_FILENAME, UPLOAD_PDF_MIMETYPE)
        response = self.api_session.post(
            f"{self.file.absolute_url()}/@tus-replace",
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(pdf_file_size),
                "Upload-Metadata": metadata,
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]

        # upload the data with PATCH
        with open(pdf_file_path, "rb") as pdf_file:
            response = self.api_session.patch(
                location,
                headers={
                    "Content-Type": "application/offset+octet-stream",
                    "Upload-Offset": "0",
                    "Tus-Resumable": "1.0.0",
                },
                data=pdf_file,
            )
        self.assertEqual(response.status_code, 204)

        transaction.commit()
        self.assertEqual(UPLOAD_PDF_FILENAME, self.file.file.filename)
        self.assertEqual(pdf_file_size, self.file.file.size)

    def test_create_with_tus_fires_proper_events(self):
        sm = getGlobalSiteManager()
        fired_events = []

        def record_event(event):
            fired_events.append(event.__class__.__name__)

        sm.registerHandler(record_event, (IObjectCreatedEvent, ))
        sm.registerHandler(record_event, (IObjectWillBeAddedEvent, ))
        sm.registerHandler(record_event, (IObjectAddedEvent, ))
        sm.registerHandler(record_event, (IObjectModifiedEvent, ))

        # initialize the upload with POST
        pdf_file_path = os.path.join(os.path.dirname(__file__),
                                     UPLOAD_PDF_FILENAME)
        pdf_file_size = os.path.getsize(pdf_file_path)
        metadata = _prepare_metadata(UPLOAD_PDF_FILENAME, UPLOAD_PDF_MIMETYPE)
        response = self.api_session.post(
            self.upload_url,
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(pdf_file_size),
                "Upload-Metadata": metadata,
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]

        # upload the data with PATCH
        with open(pdf_file_path, "rb") as pdf_file:
            response = self.api_session.patch(
                location,
                headers={
                    "Content-Type": "application/offset+octet-stream",
                    "Upload-Offset": "0",
                    "Tus-Resumable": "1.0.0",
                },
                data=pdf_file,
            )
        self.assertEqual(response.status_code, 204)

        self.assertEqual(
            fired_events,
            [
                "ObjectCreatedEvent",
                "ObjectWillBeAddedEvent",
                "ObjectAddedEvent",
                "ContainerModifiedEvent",
            ],
        )

        sm.unregisterHandler(record_event, (IObjectCreatedEvent, ))
        sm.unregisterHandler(record_event, (IObjectWillBeAddedEvent, ))
        sm.unregisterHandler(record_event, (IObjectAddedEvent, ))
        sm.unregisterHandler(record_event, (IObjectModifiedEvent, ))

    def test_replace_with_tus_fires_proper_events(self):
        # Create a test file
        self.file = api.content.create(container=self.portal,
                                       type="File",
                                       id="testfile",
                                       title="Testfile")
        transaction.commit()

        sm = getGlobalSiteManager()
        fired_events = []

        def record_event(event):
            fired_events.append(event.__class__.__name__)

        sm.registerHandler(record_event, (IObjectCreatedEvent, ))
        sm.registerHandler(record_event, (IObjectWillBeAddedEvent, ))
        sm.registerHandler(record_event, (IObjectAddedEvent, ))
        sm.registerHandler(record_event, (IObjectModifiedEvent, ))

        # initialize the upload with POST
        pdf_file_path = os.path.join(os.path.dirname(__file__),
                                     UPLOAD_PDF_FILENAME)
        pdf_file_size = os.path.getsize(pdf_file_path)
        metadata = _prepare_metadata(UPLOAD_PDF_FILENAME, UPLOAD_PDF_MIMETYPE)
        response = self.api_session.post(
            f"{self.file.absolute_url()}/@tus-replace",
            headers={
                "Tus-Resumable": "1.0.0",
                "Upload-Length": str(pdf_file_size),
                "Upload-Metadata": metadata,
            },
        )
        self.assertEqual(response.status_code, 201)
        location = response.headers["Location"]

        # upload the data with PATCH
        with open(pdf_file_path, "rb") as pdf_file:
            response = self.api_session.patch(
                location,
                headers={
                    "Content-Type": "application/offset+octet-stream",
                    "Upload-Offset": "0",
                    "Tus-Resumable": "1.0.0",
                },
                data=pdf_file,
            )
        self.assertEqual(response.status_code, 204)

        self.assertEqual(fired_events, ["ObjectModifiedEvent"])

        sm.unregisterHandler(record_event, (IObjectCreatedEvent, ))
        sm.unregisterHandler(record_event, (IObjectWillBeAddedEvent, ))
        sm.unregisterHandler(record_event, (IObjectAddedEvent, ))
        sm.unregisterHandler(record_event, (IObjectModifiedEvent, ))

    def tearDown(self):
        self.api_session.close()
        client_home = os.environ.get("CLIENT_HOME")
        tmp_dir = os.path.join(client_home, "tus-uploads")
        if os.path.isdir(tmp_dir):
            shutil.rmtree(tmp_dir)