class EmailNotificationEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Manager']) self.mailhost = getUtility(IMailHost) registry = getUtility(IRegistry) registry['plone.email_from_address'] = '*****@*****.**' registry['plone.email_from_name'] = u'Plone test site' self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.anon_api_session = RelativeSession(self.portal_url) self.anon_api_session.headers.update({'Accept': 'application/json'}) transaction.commit() def tearDown(self): self.api_session.close() def test_email_notification(self): response = self.api_session.post('/@email-notification', json={ 'from': '*****@*****.**', 'message': 'Just want to say hi.' }) transaction.commit() self.assertEqual(response.status_code, 204) self.assertTrue('Subject: [No Subject]' in self.mailhost.messages[0]) self.assertTrue('To: [email protected]' in self.mailhost.messages[0]) self.assertTrue('Reply-To: [email protected]' in self.mailhost.messages[0]) self.assertTrue('Just want to say hi.' in self.mailhost.messages[0]) def test_email_notification_all_parameters(self): response = self.api_session.post('/@email-notification', json={ 'from': '*****@*****.**', 'message': 'Just want to say hi.', 'name': 'John Doe', 'subject': 'This is the subject.' }) transaction.commit() self.assertEqual(response.status_code, 204) self.assertTrue( '=?utf-8?q?This_is_the_subject' in self.mailhost.messages[0]) self.assertTrue('To: [email protected]' in self.mailhost.messages[0]) self.assertTrue('John Doe' in self.mailhost.messages[0]) self.assertTrue('Reply-To: [email protected]' in self.mailhost.messages[0]) self.assertTrue('Just want to say hi.' in self.mailhost.messages[0])
class SerializerFunctionalTest(unittest.TestCase): """ """ layer = PLONE_APP_JSON_FIELD_FUNCTIONAL_TESTING def setUp(self): """ """ self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) def test_serializer(self): """" """ id_ = 'test-hospital' json_body = { '@type': 'TestToken', 'title': 'Test Organization xxx', 'id': id_ } with open(os.path.join(FHIR_FIXTURE_PATH, 'Organization.json'), 'r') as f: json_body['resource'] = json.load(f) response = self.api_session.post( self.portal_url, json=json_body, ) self.assertEqual(201, response.status_code) response = self.api_session.get(self.portal_url + '/' + id_) self.assertEqual(200, response.status_code) self.assertEqual(response.json()['resource']['resourceType'], json_body['resource']['resourceType'])
class TestTUSWithAT(unittest.TestCase): layer = PLONE_RESTAPI_AT_FUNCTIONAL_TESTING def setUp(self): if not HAS_AT: raise unittest.SkipTest("Skip tests if Archetypes is not present") self.portal = self.layer["portal"] setRoles(self.portal, TEST_USER_ID, ["Manager"]) login(self.portal, TEST_USER_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 = (TEST_USER_NAME, TEST_USER_PASSWORD) def tearDown(self): self.api_session.close() 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())
class TestTUSWithAT(unittest.TestCase): layer = PLONE_RESTAPI_AT_FUNCTIONAL_TESTING def setUp(self): if not HAS_AT: raise unittest.SkipTest('Skip tests if Archetypes is not present') self.portal = self.layer['portal'] setRoles(self.portal, TEST_USER_ID, ['Manager']) login(self.portal, TEST_USER_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 = (TEST_USER_NAME, TEST_USER_PASSWORD) def tearDown(self): self.api_session.close() 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())
class TestSchemaformdataEndpoint(unittest.TestCase): layer = PLONE_FORMBUILDER_ACCEPTANCE_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Manager']) # import ipdb;ipdb.set_trace() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.portal.invokeFactory( 'SchemaFormFolder', id='folder1', title='My Folder' ) # wftool = getToolByName(self.portal, 'portal_workflow') # wftool.doActionFor(self.portal.folder1, 'publish') transaction.commit() def test_post_to_folder_creates_document(self): response = self.api_session.post( self.portal.folder1.absolute_url() + '/@schemaformdata', headers={'Accept': 'application/json'}, auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), json={ "@type": "SchemaFormData", "id": "mydocument", "title": "My Document", "schema_form_data": '{"k1": "val1", "k2": 2}' }, ) self.assertEqual(201, response.status_code) transaction.begin() self.assertEqual("My Document", self.portal.folder1.mydocument.Title()) self.assertEqual("SchemaFormData", response.json().get('@type')) self.assertEqual("mydocument", response.json().get('id')) self.assertEqual("My Document", response.json().get('title')) saved_dict = response.json().get('schema_form_data') saved_dict = json.loads(saved_dict) self.assertEqual(saved_dict.get('k1'), 'val1') self.assertEqual(saved_dict.get('k2'), 2) expected_url = "http://*****:*****@id'))
class SendCompleteServiceTest(unittest.TestCase): layer = RER_NEWSLETTERPLUGIN_FLASK_API_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.request = self.layer["request"] self.portal_url = self.portal.absolute_url() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.channel = api.content.create( container=self.portal, type='Channel', title='Example channel', is_subscribable=True, ) self.channel_url = self.channel.absolute_url() transaction.commit() def tearDown(self): self.api_session.close() def test_reply_with_error_if_required_fields_not_passed(self): payload = {} response = self.api_session.post('{}/@send-complete'.format( self.channel_url), json=payload) self.assertEqual(response.status_code, 400) def test_reply_with_error_if_id_is_invalid(self): payload = {'send_uid': 'foo'} response = self.api_session.post('{}/@send-complete'.format( self.channel_url), json=payload) self.assertEqual(response.status_code, 500) def test_update_end_date_if_is_right_uid(self): adapter = getMultiAdapter((self.channel, self.request), IChannelSender) history = adapter.get_annotations_for_channel(key=HISTORY_KEY) history.append(PersistentDict({'uid': 'foo'})) transaction.commit() payload = {'send_uid': 'foo'} response = self.api_session.post('{}/@send-complete'.format( self.channel_url), json=payload) transaction.commit() self.assertEqual(response.status_code, 204) self.assertNotEqual(history[0]['send_date_end'], '---') self.assertTrue(history[0]['completed']) def test_mark_not_complete_if_pass_error_flag(self): adapter = getMultiAdapter((self.channel, self.request), IChannelSender) history = adapter.get_annotations_for_channel(key=HISTORY_KEY) history.append(PersistentDict({'uid': 'foo'})) transaction.commit() payload = {'send_uid': 'foo', 'error': True} response = self.api_session.post('{}/@send-complete'.format( self.channel_url), json=payload) transaction.commit() self.assertEqual(response.status_code, 204) self.assertNotEqual(history[0]['send_date_end'], '---') self.assertFalse(history[0]['completed'])
class TestAddons(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.ps = getToolByName(self.portal, "portal_setup") self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) def test_get_addon_record(self): response = self.api_session.get("/@addons/plone.session") self.assertEqual(response.status_code, 200) result = response.json() self.assertEqual(result["@id"], self.portal_url + u"/@addons/plone.session") self.assertEqual(result["id"], u"plone.session") # self.assertEqual(result['is_installed'], False) self.assertEqual(result["title"], u"Session refresh support") self.assertEqual(result["description"], u"Optional plone.session refresh support.") self.assertEqual(result["profile_type"], u"default") self.assertEqual(result["upgrade_info"], {}) self.assertEqual(result["install_profile_id"], u"plone.session:default") def test_get_addon_listing(self): response = self.api_session.get("/@addons") self.assertEqual(response.status_code, 200) response = response.json() self.assertIn("items", response) def test_install_uninstall_addon(self): def _get_install_status(self): response = self.api_session.get("/@addons/plone.session") result = response.json() return result["is_installed"] # Check to make sure the addon is currently shown as not installed self.assertEqual(_get_install_status(self), False) response = self.api_session.post("/@addons/plone.session/install") self.assertEqual(response.status_code, 204) self.assertEqual(safe_unicode(response.content), "") # Check to make sure the addon is currently shown as installed self.assertEqual(_get_install_status(self), True) # Now uninstall the addon response = self.api_session.post("/@addons/plone.session/uninstall") self.assertEqual(response.status_code, 204) self.assertEqual(safe_unicode(response.content), "") # Check to make sure the addon is currently shown as not installed self.assertEqual(_get_install_status(self), False) def test_install_uninstall_addon_with_representation(self): # Check to make sure the addon is currently shown as not installed response = self.api_session.get("/@addons/plone.session") result = response.json() self.assertEqual(result["is_installed"], False) # Install the addon response = self.api_session.post( "/@addons/plone.session/install", headers={"Prefer": "return=representation"}, ) self.assertEqual(response.status_code, 200) result = response.json() # Check to make sure the addon is currently shown as installed session = [a for a in result["items"] if a["id"] == u"plone.session"] self.assertEqual(len(session), 1) self.assertTrue(session[0]["is_installed"]) # Now uninstall the addon response = self.api_session.post( "/@addons/plone.session/uninstall", headers={"Prefer": "return=representation"}, ) self.assertEqual(response.status_code, 200) result = response.json() # Check to make sure the addon is currently shown as not installed session = [a for a in result["items"] if a["id"] == u"plone.session"] self.assertEqual(len(session), 1) self.assertFalse(session[0]["is_installed"]) def test_upgrade_addon(self): def _get_upgrade_info(self): response = self.api_session.get("/@addons/plone.restapi") result = response.json() return result["upgrade_info"] # Set need upgrade state self.ps.setLastVersionForProfile("plone.restapi:default", "0002") transaction.commit() self.assertEqual( { "available": True, "hasProfile": True, "installedVersion": "0002", "newVersion": "0006", "required": True, }, _get_upgrade_info(self), ) # Now call the upgrade response = self.api_session.post("/@addons/plone.restapi/upgrade") self.assertEqual(response.status_code, 204) self.assertEqual(safe_unicode(response.content), "") self.assertEqual( { "available": False, "hasProfile": True, "installedVersion": "0006", "newVersion": "0006", "required": False, }, _get_upgrade_info(self), ) def test_upgrade_addon_with_representation(self): response = self.api_session.get("/@addons/plone.restapi") result = response.json() last_version = result["upgrade_info"] # Set need upgrade state self.ps.setLastVersionForProfile("plone.restapi:default", "0002") transaction.commit() response = self.api_session.get("/@addons/plone.restapi") result = response.json() self.assertEqual( { "available": True, "hasProfile": True, "installedVersion": "0002", "newVersion": last_version["newVersion"], "required": True, }, result["upgrade_info"], ) # Now call the upgrade response = self.api_session.post( "/@addons/plone.restapi/upgrade", headers={"Prefer": "return=representation"}, ) self.assertEqual(response.status_code, 200) result = response.json() # Check to make sure the addon is at last version session = [a for a in result["items"] if a["id"] == u"plone.restapi"] self.assertEqual(len(session), 1) self.assertEqual(last_version, session[0]["upgrade_info"])
class TestLocking(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.portal = self.layer['portal'] self.request = self.layer['request'] login(self.portal, SITE_OWNER_NAME) self.doc = self.portal[self.portal.invokeFactory('Document', id='doc1', title='My Document')] alsoProvides(self.doc, ITTWLockable) self.api_session = RelativeSession(self.doc.absolute_url()) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) transaction.commit() def tearDown(self): self.api_session.close() def test_lock_object(self): response = self.api_session.post('/@lock', ) transaction.commit() self.assertEqual(response.status_code, 200) self.assertTrue(ILockable(self.doc).locked()) def test_lock_object_non_stealable(self): response = self.api_session.post('/@lock', json={'stealable': False}) transaction.commit() self.assertEqual(response.status_code, 200) self.assertTrue(INonStealableLock.providedBy(self.doc)) def test_lock_object_with_custom_timeout(self): response = self.api_session.post('/@lock', json={'timeout': 86400}) transaction.commit() self.assertEqual(response.status_code, 200) self.assertEqual(self.doc.wl_lockValues()[0].getTimeout(), 86400) def test_unlock_object(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.post('/@unlock') transaction.commit() self.assertEqual(response.status_code, 200) self.assertFalse(lockable.locked()) def test_refresh_lock(self): lockable = ILockable(self.doc) lockable.lock() modified = self.doc.wl_lockValues()[0].getModifiedTime() transaction.commit() response = self.api_session.post('/@refresh-lock') transaction.commit() self.assertEqual(response.status_code, 200) self.assertTrue( self.doc.wl_lockValues()[0].getModifiedTime() > modified) def test_lock_info_for_locked_object(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.get('/@lock') self.assertEqual(response.status_code, 200) self.assertTrue(response.json()['locked']) def test_lock_info_for_unlocked_object(self): response = self.api_session.get('/@lock') self.assertEqual(response.status_code, 200) self.assertFalse(response.json()['locked']) def test_update_locked_object_without_token_fails(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.patch('/', json={'title': 'New Title'}) transaction.commit() self.assertEqual(response.status_code, 403) self.assertEqual(self.doc.Title(), 'My Document') def test_update_locked_object_with_token_succeeds(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.patch( '/', headers={'Lock-Token': lockable.lock_info()[0]['token']}, json={'title': 'New Title'}) transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(self.doc.Title(), 'New Title')
class TestTraversal(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.request = self.layer['request'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) setRoles(self.portal, TEST_USER_ID, ['Manager']) self.portal.invokeFactory('Document', id='front-page') self.document = self.portal['front-page'] self.document.title = u"Welcome to Plone" self.document.description = \ u"Congratulations! You have successfully installed Plone." self.document.text = RichTextValue( u"If you're seeing this instead of the web site you were " + u"expecting, the owner of this web site has just installed " + u"Plone. Do not contact the Plone Team or the Plone mailing " + u"lists about this.", 'text/plain', 'text/html' ) self.document.creation_date = DateTime('2016-01-21T01:14:48+00:00') IMutableUUID(self.document).set('1f699ffa110e45afb1ba502f75f7ec33') self.document.reindexObject() self.document.modification_date = DateTime('2016-01-21T01:24:11+00:00') import transaction transaction.commit() self.browser = Browser(self.app) self.browser.handleErrors = False self.browser.addHeader( 'Authorization', 'Basic %s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,) ) def test_documentation_document(self): response = self.api_session.get(self.document.absolute_url()) save_response_for_documentation('document.json', response) def test_documentation_news_item(self): self.portal.invokeFactory('News Item', id='newsitem') self.portal.newsitem.title = 'My News Item' self.portal.newsitem.description = u'This is a news item' self.portal.newsitem.text = RichTextValue( u"Lorem ipsum", 'text/plain', 'text/html' ) image_file = os.path.join(os.path.dirname(__file__), u'image.png') self.portal.newsitem.image = NamedBlobImage( data=open(image_file, 'r').read(), contentType='image/png', filename=u'image.png' ) self.portal.newsitem.image_caption = u'This is an image caption.' self.portal.newsitem.creation_date = DateTime( '2016-01-21T02:14:48+00:00') self.portal.newsitem.modification_date = DateTime( '2016-01-21T02:24:11+00:00') IMutableUUID(self.portal.newsitem).set( '80c2a074cb4240d08c9a129e3a834c74') import transaction transaction.commit() response = self.api_session.get(self.portal.newsitem.absolute_url()) save_response_for_documentation('newsitem.json', response) def test_documentation_event(self): self.portal.invokeFactory('Event', id='event') self.portal.event.title = 'Event' self.portal.event.description = u'This is an event' self.portal.event.start = datetime(2013, 1, 1, 10, 0) self.portal.event.end = datetime(2013, 1, 1, 12, 0) self.portal.event.creation_date = DateTime('2016-01-21T03:14:48+00:00') self.portal.event.modification_date = DateTime( '2016-01-21T03:24:11+00:00') IMutableUUID(self.portal.event).set('846d632bc0854c5aa6d3dcae171ed2db') import transaction transaction.commit() response = self.api_session.get(self.portal.event.absolute_url()) save_response_for_documentation('event.json', response) def test_documentation_link(self): self.portal.invokeFactory('Link', id='link') self.portal.link.title = 'My Link' self.portal.link.description = u'This is a link' self.portal.remoteUrl = 'http://plone.org' self.portal.link.creation_date = DateTime('2016-01-21T04:14:48+00:00') self.portal.link.modification_date = DateTime( '2016-01-21T04:24:11+00:00') IMutableUUID(self.portal.link).set('6ff48d27762143a0ae8d63cee73d9fc2') import transaction transaction.commit() response = self.api_session.get(self.portal.link.absolute_url()) save_response_for_documentation('link.json', response) def test_documentation_file(self): self.portal.invokeFactory('File', id='file') self.portal.file.title = 'My File' self.portal.file.description = u'This is a file' pdf_file = os.path.join( os.path.dirname(__file__), u'file.pdf' ) self.portal.file.file = NamedBlobFile( data=open(pdf_file, 'r').read(), contentType='application/pdf', filename=u'file.pdf' ) intids = getUtility(IIntIds) file_id = intids.getId(self.portal.file) self.portal.file.file = RelationValue(file_id) self.portal.file.creation_date = DateTime('2016-01-21T05:14:48+00:00') self.portal.file.modification_date = DateTime( '2016-01-21T05:24:11+00:00') IMutableUUID(self.portal.file).set('9b6a4eadb9074dde97d86171bb332ae9') import transaction transaction.commit() response = self.api_session.get(self.portal.file.absolute_url()) save_response_for_documentation('file.json', response) def test_documentation_image(self): self.portal.invokeFactory('Image', id='image') self.portal.image.title = 'My Image' self.portal.image.description = u'This is an image' image_file = os.path.join(os.path.dirname(__file__), u'image.png') self.portal.image.image = NamedBlobImage( data=open(image_file, 'r').read(), contentType='image/png', filename=u'image.png' ) self.portal.image.creation_date = DateTime('2016-01-21T06:14:48+00:00') self.portal.image.modification_date = DateTime( '2016-01-21T06:24:11+00:00') IMutableUUID(self.portal.image).set('2166e81a0c224fe3b62e197c7fdc9c3e') import transaction transaction.commit() response = self.api_session.get(self.portal.image.absolute_url()) save_response_for_documentation('image.json', response) def test_documentation_folder(self): self.portal.invokeFactory('Folder', id='folder') self.portal.folder.title = 'My Folder' self.portal.folder.description = u'This is a folder with two documents' self.portal.folder.invokeFactory( 'Document', id='doc1', title='A document within a folder' ) self.portal.folder.invokeFactory( 'Document', id='doc2', title='A document within a folder' ) self.portal.folder.creation_date = DateTime( '2016-01-21T07:14:48+00:00') self.portal.folder.modification_date = DateTime( '2016-01-21T07:24:11+00:00') IMutableUUID(self.portal.folder).set( 'fc7881c46d61452db4177bc059d9dcb5') import transaction transaction.commit() response = self.api_session.get(self.portal.folder.absolute_url()) save_response_for_documentation('folder.json', response) def test_documentation_collection(self): self.portal.invokeFactory('Collection', id='collection') self.portal.collection.title = 'My Collection' self.portal.collection.description = \ u'This is a collection with two documents' self.portal.collection.query = [{ 'i': 'portal_type', 'o': 'plone.app.querystring.operation.string.is', 'v': 'Document', }] self.portal.invokeFactory( 'Document', id='doc1', title='Document 1' ) self.portal.invokeFactory( 'Document', id='doc2', title='Document 2' ) self.portal.collection.creation_date = DateTime( '2016-01-21T08:14:48+00:00') self.portal.collection.modification_date = DateTime( '2016-01-21T08:24:11+00:00') IMutableUUID(self.portal.collection).set( 'd0c89bc77f874ce1aad5720921d875c0') import transaction transaction.commit() response = self.api_session.get(self.portal.collection.absolute_url()) save_response_for_documentation('collection.json', response) def test_documentation_siteroot(self): response = self.api_session.get(self.portal.absolute_url()) save_response_for_documentation('siteroot.json', response) def test_documentation_404_not_found(self): response = self.api_session.get('non-existing-resource') save_response_for_documentation('404_not_found.json', response) def test_documentation_search(self): query = {'sort_on': 'path'} response = self.api_session.get('/search', params=query) save_response_for_documentation('search.json', response) def test_documentation_workflow(self): response = self.api_session.get( '{}/workflow'.format(self.document.absolute_url())) save_response_for_documentation('workflow_get.json', response) def test_documentation_workflow_transition(self): response = self.api_session.post( '{}/workflow/publish'.format(self.document.absolute_url())) save_response_for_documentation('workflow_post.json', response) def test_documentation_registry_get(self): response = self.api_session.get( '/@registry/plone.app.querystring.field.path.title') save_response_for_documentation('registry_get.json', response) def test_documentation_registry_update(self): response = self.api_session.patch( '/@registry/', json={'plone.app.querystring.field.path.title': 'Value'}) save_response_for_documentation('registry_update.json', response) def test_documentation_types(self): response = self.api_session.get('/@types') save_response_for_documentation('types.json', response) def test_documentation_types_document(self): response = self.api_session.get('@types/Document') save_response_for_documentation('types_document.json', response)
class TestTraversal(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.request = self.layer['request'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() self.time_freezer = freeze_time("2016-10-21 19:00:00") self.frozen_time = self.time_freezer.start() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) setRoles(self.portal, TEST_USER_ID, ['Manager']) self.portal.invokeFactory('Document', id='front-page') self.document = self.portal['front-page'] self.document.title = u"Welcome to Plone" self.document.description = \ u"Congratulations! You have successfully installed Plone." self.document.text = RichTextValue( u"If you're seeing this instead of the web site you were " + u"expecting, the owner of this web site has just installed " + u"Plone. Do not contact the Plone Team or the Plone mailing " + u"lists about this.", 'text/plain', 'text/html') self.document.creation_date = DateTime('2016-01-21T01:14:48+00:00') IMutableUUID(self.document).set('1f699ffa110e45afb1ba502f75f7ec33') self.document.reindexObject() self.document.modification_date = DateTime('2016-01-21T01:24:11+00:00') import transaction transaction.commit() self.browser = Browser(self.app) self.browser.handleErrors = False self.browser.addHeader( 'Authorization', 'Basic %s:%s' % ( SITE_OWNER_NAME, SITE_OWNER_PASSWORD, )) def tearDown(self): self.time_freezer.stop() def test_documentation_document(self): response = self.api_session.get(self.document.absolute_url()) save_response_for_documentation('document.json', response) def test_documentation_news_item(self): self.portal.invokeFactory('News Item', id='newsitem') self.portal.newsitem.title = 'My News Item' self.portal.newsitem.description = u'This is a news item' self.portal.newsitem.text = RichTextValue(u"Lorem ipsum", 'text/plain', 'text/html') image_file = os.path.join(os.path.dirname(__file__), u'image.png') self.portal.newsitem.image = NamedBlobImage(data=open(image_file, 'r').read(), contentType='image/png', filename=u'image.png') self.portal.newsitem.image_caption = u'This is an image caption.' self.portal.newsitem.creation_date = DateTime( '2016-01-21T02:14:48+00:00') self.portal.newsitem.modification_date = DateTime( '2016-01-21T02:24:11+00:00') IMutableUUID( self.portal.newsitem).set('80c2a074cb4240d08c9a129e3a834c74') import transaction transaction.commit() response = self.api_session.get(self.portal.newsitem.absolute_url()) save_response_for_documentation('newsitem.json', response) def test_documentation_event(self): self.portal.invokeFactory('Event', id='event') self.portal.event.title = 'Event' self.portal.event.description = u'This is an event' self.portal.event.start = datetime(2013, 1, 1, 10, 0) self.portal.event.end = datetime(2013, 1, 1, 12, 0) self.portal.event.creation_date = DateTime('2016-01-21T03:14:48+00:00') self.portal.event.modification_date = DateTime( '2016-01-21T03:24:11+00:00') IMutableUUID(self.portal.event).set('846d632bc0854c5aa6d3dcae171ed2db') import transaction transaction.commit() response = self.api_session.get(self.portal.event.absolute_url()) save_response_for_documentation('event.json', response) def test_documentation_link(self): self.portal.invokeFactory('Link', id='link') self.portal.link.title = 'My Link' self.portal.link.description = u'This is a link' self.portal.remoteUrl = 'http://plone.org' self.portal.link.creation_date = DateTime('2016-01-21T04:14:48+00:00') self.portal.link.modification_date = DateTime( '2016-01-21T04:24:11+00:00') IMutableUUID(self.portal.link).set('6ff48d27762143a0ae8d63cee73d9fc2') import transaction transaction.commit() response = self.api_session.get(self.portal.link.absolute_url()) save_response_for_documentation('link.json', response) def test_documentation_file(self): self.portal.invokeFactory('File', id='file') self.portal.file.title = 'My File' self.portal.file.description = u'This is a file' pdf_file = os.path.join(os.path.dirname(__file__), u'file.pdf') self.portal.file.file = NamedBlobFile(data=open(pdf_file, 'r').read(), contentType='application/pdf', filename=u'file.pdf') self.portal.file.creation_date = DateTime('2016-01-21T05:14:48+00:00') self.portal.file.modification_date = DateTime( '2016-01-21T05:24:11+00:00') IMutableUUID(self.portal.file).set('9b6a4eadb9074dde97d86171bb332ae9') import transaction transaction.commit() response = self.api_session.get(self.portal.file.absolute_url()) save_response_for_documentation('file.json', response) def test_documentation_image(self): self.portal.invokeFactory('Image', id='image') self.portal.image.title = 'My Image' self.portal.image.description = u'This is an image' image_file = os.path.join(os.path.dirname(__file__), u'image.png') self.portal.image.image = NamedBlobImage(data=open(image_file, 'r').read(), contentType='image/png', filename=u'image.png') self.portal.image.creation_date = DateTime('2016-01-21T06:14:48+00:00') self.portal.image.modification_date = DateTime( '2016-01-21T06:24:11+00:00') IMutableUUID(self.portal.image).set('2166e81a0c224fe3b62e197c7fdc9c3e') import transaction transaction.commit() response = self.api_session.get(self.portal.image.absolute_url()) save_response_for_documentation('image.json', response) def test_documentation_folder(self): self.portal.invokeFactory('Folder', id='folder') self.portal.folder.title = 'My Folder' self.portal.folder.description = u'This is a folder with two documents' self.portal.folder.invokeFactory('Document', id='doc1', title='A document within a folder') self.portal.folder.invokeFactory('Document', id='doc2', title='A document within a folder') self.portal.folder.creation_date = DateTime( '2016-01-21T07:14:48+00:00') self.portal.folder.modification_date = DateTime( '2016-01-21T07:24:11+00:00') IMutableUUID( self.portal.folder).set('fc7881c46d61452db4177bc059d9dcb5') import transaction transaction.commit() response = self.api_session.get(self.portal.folder.absolute_url()) save_response_for_documentation('folder.json', response) def test_documentation_collection(self): self.portal.invokeFactory('Collection', id='collection') self.portal.collection.title = 'My Collection' self.portal.collection.description = \ u'This is a collection with two documents' self.portal.collection.query = [{ 'i': 'portal_type', 'o': 'plone.app.querystring.operation.string.is', 'v': 'Document', }] self.portal.invokeFactory('Document', id='doc1', title='Document 1') self.portal.invokeFactory('Document', id='doc2', title='Document 2') self.portal.collection.creation_date = DateTime( '2016-01-21T08:14:48+00:00') self.portal.collection.modification_date = DateTime( '2016-01-21T08:24:11+00:00') IMutableUUID( self.portal.collection).set('d0c89bc77f874ce1aad5720921d875c0') import transaction transaction.commit() response = self.api_session.get(self.portal.collection.absolute_url()) save_response_for_documentation('collection.json', response) def test_documentation_siteroot(self): response = self.api_session.get(self.portal.absolute_url()) save_response_for_documentation('siteroot.json', response) def test_documentation_404_not_found(self): response = self.api_session.get('non-existing-resource') save_response_for_documentation('404_not_found.json', response) def test_documentation_search(self): query = {'sort_on': 'path'} response = self.api_session.get('/@search', params=query) save_response_for_documentation('search.json', response) def test_documentation_workflow(self): response = self.api_session.get('{}/@workflow'.format( self.document.absolute_url())) save_response_for_documentation('workflow_get.json', response) def test_documentation_workflow_transition(self): self.frozen_time.tick(timedelta(minutes=5)) response = self.api_session.post('{}/@workflow/publish'.format( self.document.absolute_url())) save_response_for_documentation('workflow_post.json', response) def test_documentation_registry_get(self): response = self.api_session.get( '/@registry/plone.app.querystring.field.path.title') save_response_for_documentation('registry_get.json', response) def test_documentation_registry_update(self): response = self.api_session.patch( '/@registry/', json={'plone.app.querystring.field.path.title': 'Value'}) save_response_for_documentation('registry_update.json', response) def test_documentation_types(self): response = self.api_session.get('/@types') save_response_for_documentation('types.json', response) def test_documentation_types_document(self): response = self.api_session.get('@types/Document') save_response_for_documentation('types_document.json', response) def test_documentation_login(self): self.portal.acl_users.jwt_auth._secret = 'secret' self.portal.acl_users.jwt_auth.use_keyring = False self.portal.acl_users.jwt_auth.token_timeout = 0 import transaction transaction.commit() self.api_session.auth = None response = self.api_session.post('{}/@login'.format( self.portal.absolute_url()), json={ 'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD }) save_response_for_documentation('login.json', response) def test_documentation_login_renew(self): self.portal.acl_users.jwt_auth._secret = 'secret' self.portal.acl_users.jwt_auth.use_keyring = False self.portal.acl_users.jwt_auth.token_timeout = 0 import transaction transaction.commit() self.api_session.auth = None response = self.api_session.post('{}/@login'.format( self.portal.absolute_url()), json={ 'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD }) token = json.loads(response.content)['token'] response = self.api_session.post( '{}/@login-renew'.format(self.portal.absolute_url()), headers={'Authorization': 'Bearer {}'.format(token)}) save_response_for_documentation('login_renew.json', response) def test_documentation_logout(self): self.portal.acl_users.jwt_auth._secret = 'secret' self.portal.acl_users.jwt_auth.use_keyring = False self.portal.acl_users.jwt_auth.token_timeout = 0 self.portal.acl_users.jwt_auth.store_tokens = True import transaction transaction.commit() self.api_session.auth = None response = self.api_session.post('{}/@login'.format( self.portal.absolute_url()), json={ 'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD }) token = json.loads(response.content)['token'] response = self.api_session.post( '{}/@logout'.format(self.portal.absolute_url()), headers={'Authorization': 'Bearer {}'.format(token)}) save_response_for_documentation('logout.json', response) def test_documentation_batching(self): folder = self.portal[self.portal.invokeFactory('Folder', id='folder', title='Folder')] for i in range(7): folder.invokeFactory('Document', id='doc-%s' % str(i + 1), title='Document %s' % str(i + 1)) transaction.commit() query = {'sort_on': 'path'} response = self.api_session.get('/folder/@search?b_size=5', params=query) save_response_for_documentation('batching.json', response) def test_documentation_users(self): test_user = api.user.get(username=TEST_USER_ID) properties = { "description": "This is a test user", "email": "*****@*****.**", "fullname": "Test User", "home_page": "http://www.example.com", "location": "Bonn", "username": "******" } test_user.setMemberProperties(mapping=properties) admin = api.user.get(username='******') properties = { "description": "This is an admin user", "email": "*****@*****.**", "fullname": "Administrator", "home_page": "http://www.example.com", "location": "Berlin", "username": "******" } admin.setMemberProperties(mapping=properties) transaction.commit() response = self.api_session.get('/@users') save_response_for_documentation('users.json', response) def test_documentation_users_get(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create(email='*****@*****.**', username='******', properties=properties) transaction.commit() response = self.api_session.get('@users/noam') save_response_for_documentation('users_get.json', response) def test_documentation_users_created(self): response = self.api_session.post( '/@users', json={ 'username': '******', 'email': '*****@*****.**', 'password': '******', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' }, ) save_response_for_documentation('users_created.json', response) def test_documentation_users_update(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create(email='*****@*****.**', username='******', properties=properties) transaction.commit() response = self.api_session.patch( '/@users/noam', json={ 'email': '*****@*****.**', }, ) save_response_for_documentation('users_update.json', response) def test_documentation_users_delete(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create(email='*****@*****.**', username='******', properties=properties) transaction.commit() response = self.api_session.delete('/@users/noam') save_response_for_documentation('users_delete.json', response) def test_documentation_breadcrumbs(self): response = self.api_session.get('{}/@components/breadcrumbs'.format( self.document.absolute_url())) save_response_for_documentation('breadcrumbs.json', response) def test_documentation_navigation(self): response = self.api_session.get('{}/@components/navigation'.format( self.document.absolute_url())) save_response_for_documentation('navigation.json', response)
class TestLocking(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.portal = self.layer["portal"] self.request = self.layer["request"] login(self.portal, SITE_OWNER_NAME) self.doc = self.portal[self.portal.invokeFactory("Document", id="doc1", title="My Document")] alsoProvides(self.doc, ITTWLockable) self.api_session = RelativeSession(self.doc.absolute_url(), test=self) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) transaction.commit() def tearDown(self): self.api_session.close() def test_lock_object(self): response = self.api_session.post("/@lock") transaction.commit() self.assertEqual(response.status_code, 200) self.assertTrue(ILockable(self.doc).locked()) def test_lock_object_non_stealable(self): response = self.api_session.post("/@lock", json={"stealable": False}) transaction.commit() self.assertEqual(response.status_code, 200) self.assertTrue(INonStealableLock.providedBy(self.doc)) def test_lock_object_with_custom_timeout(self): response = self.api_session.post("/@lock", json={"timeout": 86400}) transaction.commit() self.assertEqual(response.status_code, 200) self.assertEqual(self.doc.wl_lockValues()[0].getTimeout(), 86400) def test_unlock_object(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.delete("/@lock") transaction.commit() self.assertEqual(response.status_code, 200) self.assertFalse(lockable.locked()) def test_refresh_lock(self): lockable = ILockable(self.doc) lockable.lock() modified = self.doc.wl_lockValues()[0].getModifiedTime() transaction.commit() response = self.api_session.patch("/@lock") transaction.commit() self.assertEqual(response.status_code, 200) self.assertTrue( self.doc.wl_lockValues()[0].getModifiedTime() > modified) def test_lock_info_for_locked_object(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.get("/@lock") self.assertEqual(response.status_code, 200) self.assertTrue(response.json()["locked"]) def test_lock_info_for_unlocked_object(self): response = self.api_session.get("/@lock") self.assertEqual(response.status_code, 200) self.assertFalse(response.json()["locked"]) def test_update_locked_object_without_token_fails(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.patch("/", json={"title": "New Title"}) transaction.commit() self.assertEqual(response.status_code, 403) self.assertEqual(self.doc.Title(), "My Document") def test_update_locked_object_with_token_succeeds(self): lockable = ILockable(self.doc) lockable.lock() transaction.commit() response = self.api_session.patch( "/", headers={"Lock-Token": lockable.lock_info()[0]["token"]}, json={"title": "New Title"}, ) transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(self.doc.Title(), "New Title")
class TestUsersEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Manager']) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create( email='*****@*****.**', username='******', properties=properties ) transaction.commit() def test_list_users(self): response = self.api_session.get('/@users') self.assertEqual(200, response.status_code) self.assertEqual(3, len(response.json())) user_ids = [user['id'] for user in response.json()] self.assertIn('admin', user_ids) self.assertIn('test_user_1_', user_ids) self.assertIn('noam', user_ids) noam = [x for x in response.json() if x.get('username') == 'noam'][0] self.assertEqual('noam', noam.get('id')) self.assertEqual( self.portal.absolute_url() + '/@users/noam', noam.get('@id') ) self.assertEqual('*****@*****.**', noam.get('email')) self.assertEqual('Noam Avram Chomsky', noam.get('fullname')) self.assertEqual('web.mit.edu/chomsky', noam.get('home_page')) # noqa self.assertEqual('Professor of Linguistics', noam.get('description')) # noqa self.assertEqual('Cambridge, MA', noam.get('location')) def test_add_user(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) howard = api.user.get(userid='howard') self.assertEqual( "*****@*****.**", howard.getProperty('email') ) def test_add_user_username_is_required(self): response = self.api_session.post( '/@users', json={ "password": "******" }, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue('"Property \'username\' is required' in response.text) def test_add_user_password_is_required(self): response = self.api_session.post( '/@users', json={ "username": "******" }, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue('"Property \'password\' is required' in response.text) def test_add_user_email_is_required_if_email_login_is_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "username": "******", "password": "******" }, ) self.assertEqual(400, response.status_code) self.assertTrue('"Property \'email\' is required' in response.text) def test_add_user_email_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) self.assertTrue(api.user.get(userid='*****@*****.**')) def test_add_user_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) user = api.user.get(userid='*****@*****.**') self.assertTrue(user) self.assertEqual('*****@*****.**', user.getUserName()) self.assertEqual('*****@*****.**', user.getProperty('email')) def test_get_user(self): response = self.api_session.get('/@users/noam') self.assertEqual(response.status_code, 200) self.assertEqual('noam', response.json().get('id')) self.assertEqual( self.portal.absolute_url() + '/@users/noam', response.json().get('@id') ) self.assertEqual( '*****@*****.**', response.json().get('email') ) self.assertEqual('Noam Avram Chomsky', response.json().get('fullname')) self.assertEqual('web.mit.edu/chomsky', response.json().get('home_page')) # noqa self.assertEqual('Professor of Linguistics', response.json().get('description')) # noqa self.assertEqual('Cambridge, MA', response.json().get('location')) def test_get_search_user_with_filter(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() response = self.api_session.get('/@users', params={'query': 'noa'}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual('noam', response.json()[0].get('id')) self.assertEqual( self.portal.absolute_url() + '/@users/noam', response.json()[0].get('@id') ) self.assertEqual( '*****@*****.**', response.json()[0].get('email') ) self.assertEqual('Noam Avram Chomsky', response.json()[0].get('fullname')) # noqa response = self.api_session.get('/@users', params={'query': 'howa'}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual('howard', response.json()[0].get('id')) def test_get_non_existing_user(self): response = self.api_session.get('/@users/non-existing-user') self.assertEqual(response.status_code, 404) def test_update_user(self): payload = { 'fullname': 'Noam A. Chomsky', 'username': '******', 'email': '*****@*****.**' } response = self.api_session.patch('/@users/noam', json=payload) transaction.commit() self.assertEqual(response.status_code, 204) noam = api.user.get(userid='noam') self.assertEqual('noam', noam.getUserId()) # user id never changes self.assertEqual('avram', noam.getUserName()) self.assertEqual('Noam A. Chomsky', noam.getProperty('fullname')) self.assertEqual( '*****@*****.**', noam.getProperty('email') ) def test_update_user_password(self): old_password_hashes = dict( self.portal.acl_users.source_users._user_passwords ) payload = {'password': '******'} self.api_session.patch('/@users/noam', json=payload) transaction.commit() new_password_hashes = dict( self.portal.acl_users.source_users._user_passwords ) self.assertNotEqual( old_password_hashes['noam'], new_password_hashes['noam'] ) def test_delete_user(self): response = self.api_session.delete('/@users/noam') transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(None, api.user.get(userid='noam')) def test_delete_non_existing_user(self): response = self.api_session.delete('/@users/non-existing-user') transaction.commit() self.assertEqual(response.status_code, 404)
class TestCaptcha(unittest.TestCase): layer = VOLTO_FORMSUPPORT_API_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.mailhost = getUtility(IMailHost) self.registry = getUtility(IRegistry) self.registry["plone.email_from_address"] = "*****@*****.**" self.registry["plone.email_from_name"] = "Plone test site" self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.anon_api_session = RelativeSession(self.portal_url) self.anon_api_session.headers.update({"Accept": "application/json"}) self.document = api.content.create( type="Document", title="Example context", container=self.portal, ) self.document.blocks = { "text-id": { "@type": "text" }, "form-id": { "@type": "form" }, } self.document_url = self.document.absolute_url() transaction.commit() def tearDown(self): self.api_session.close() self.anon_api_session.close() # set default block self.document.blocks = { "text-id": { "@type": "text" }, "form-id": { "@type": "form" }, } transaction.commit() def submit_form(self, data): url = "{}/@submit-form".format(self.document_url) response = self.api_session.post( url, json=data, ) # transaction.commit() return response def test_recaptcha_no_settings(self): self.document.blocks = { "text-id": { "@type": "text" }, "form-id": { "@type": "form", "default_subject": "block subject", "default_from": "*****@*****.**", "send": True, "subblocks": [ { "field_id": "contact", "field_type": "from", "use_as_bcc": True, }, ], "captcha": "recaptcha", }, } self.registry.registerInterface(IReCaptchaSettings) transaction.commit() response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", }, ) transaction.commit() self.assertEqual(response.status_code, 500) self.assertEqual( response.json()["message"], "No recaptcha private key configured. Go to path/to/site/@@recaptcha-settings " "to configure.", ) def test_recaptcha(self): self.registry.registerInterface(IReCaptchaSettings) settings = self.registry.forInterface(IReCaptchaSettings) settings.public_key = "public" settings.private_key = "private" self.document.blocks = { "text-id": { "@type": "text" }, "form-id": { "@type": "form", "default_subject": "block subject", "default_from": "*****@*****.**", "send": True, "subblocks": [ { "field_id": "contact", "field_type": "from", "use_as_bcc": True, }, ], "captcha": "recaptcha", }, } transaction.commit() response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", }, ) transaction.commit() self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["message"], "No captcha token provided.") with patch("collective.volto.formsupport.captcha.recaptcha.submit" ) as mock_submit: mock_submit.return_value = Mock(is_valid=False) response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", "captcha": { "token": "12345" }, }, ) transaction.commit() mock_submit.assert_called_once_with("12345", "private", "127.0.0.1") self.assertEqual(response.status_code, 400) self.assertEqual( response.json()["message"], "The code you entered was wrong, please enter the new one.", ) with patch("collective.volto.formsupport.captcha.recaptcha.submit" ) as mock_submit: mock_submit.return_value = Mock(is_valid=True) response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", "captcha": { "token": "12345" }, }, ) transaction.commit() mock_submit.assert_called_once_with("12345", "private", "127.0.0.1") self.assertEqual(response.status_code, 204) def test_hcaptcha(self, ): self.registry.registerInterface(IHCaptchaSettings) settings = self.registry.forInterface(IHCaptchaSettings) settings.public_key = "public" settings.private_key = "private" self.document.blocks = { "text-id": { "@type": "text" }, "form-id": { "@type": "form", "default_subject": "block subject", "default_from": "*****@*****.**", "send": True, "subblocks": [ { "field_id": "contact", "field_type": "from", "use_as_bcc": True, }, ], "captcha": "hcaptcha", }, } transaction.commit() response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", }, ) transaction.commit() self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["message"], "No captcha token provided.") with patch("collective.volto.formsupport.captcha.hcaptcha.submit" ) as mock_submit: mock_submit.return_value = Mock(is_valid=False) response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", "captcha": { "token": "12345" }, }, ) transaction.commit() mock_submit.assert_called_once_with("12345", "private", "127.0.0.1") self.assertEqual(response.status_code, 400) self.assertEqual( response.json()["message"], "The code you entered was wrong, please enter the new one.", ) with patch("collective.volto.formsupport.captcha.hcaptcha.submit" ) as mock_submit: mock_submit.return_value = Mock(is_valid=True) response = self.submit_form(data={ "data": [ { "label": "Message", "value": "just want to say hi" }, ], "block_id": "form-id", "captcha": { "token": "12345" }, }, ) transaction.commit() mock_submit.assert_called_once_with("12345", "private", "127.0.0.1") self.assertEqual(response.status_code, 204) def test_get_vocabulary(self): response = self.api_session.get( "/@vocabularies/collective.volto.formsupport.captcha.providers") self.assertEqual(response.status_code, 200) data = response.json() # no adapters configured self.assertTrue(data["@id"].endswith( "@vocabularies/collective.volto.formsupport.captcha.providers")) self.assertEqual(data["items_total"], 0) self.assertEqual(data["items"], []) # configure recaptcha self.registry.registerInterface(IReCaptchaSettings) settings = self.registry.forInterface(IReCaptchaSettings) settings.public_key = "public" settings.private_key = "private" transaction.commit() response = self.api_session.get( "/@vocabularies/collective.volto.formsupport.captcha.providers") self.assertEqual(response.status_code, 200) data = response.json() # no adapters configured self.assertTrue(data["@id"].endswith( "@vocabularies/collective.volto.formsupport.captcha.providers")) self.assertEqual(data["items_total"], 1) self.assertEqual(data["items"], [{ "title": "Google ReCaptcha", "token": "recaptcha" }])
class TestUsersEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Manager']) self.mailhost = getUtility(IMailHost) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.anon_api_session = RelativeSession(self.portal_url) self.anon_api_session.headers.update({'Accept': 'application/json'}) properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create(email='*****@*****.**', username='******', properties=properties, password=u'password') properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Other user', } api.user.create(email='*****@*****.**', username='******', properties=properties, password=u'otherpassword') transaction.commit() def tearDown(self): self.api_session.close() self.anon_api_session.close() def test_list_users(self): response = self.api_session.get('/@users') self.assertEqual(200, response.status_code) self.assertEqual(4, len(response.json())) user_ids = [user['id'] for user in response.json()] self.assertIn('admin', user_ids) self.assertIn('test_user_1_', user_ids) self.assertIn('noam', user_ids) noam = [x for x in response.json() if x.get('username') == 'noam'][0] self.assertEqual('noam', noam.get('id')) self.assertEqual(self.portal.absolute_url() + '/@users/noam', noam.get('@id')) self.assertEqual('*****@*****.**', noam.get('email')) self.assertEqual('Noam Avram Chomsky', noam.get('fullname')) self.assertEqual('web.mit.edu/chomsky', noam.get('home_page')) # noqa self.assertEqual('Professor of Linguistics', noam.get('description')) # noqa self.assertEqual('Cambridge, MA', noam.get('location')) def test_list_users_without_being_manager(self): noam_api_session = RelativeSession(self.portal_url) noam_api_session.headers.update({'Accept': 'application/json'}) noam_api_session.auth = ('noam', 'password') response = noam_api_session.get('/@users') self.assertEqual(response.status_code, 401) noam_api_session.close() def test_list_users_as_anonymous(self): response = self.anon_api_session.get('/@users') self.assertEqual(response.status_code, 401) def test_add_user(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******", "roles": [ "Contributor", ], }, ) transaction.commit() self.assertEqual(201, response.status_code) howard = api.user.get(userid='howard') self.assertEqual("*****@*****.**", howard.getProperty('email')) self.assertIn('Contributor', api.user.get_roles(username="******")) def test_add_user_username_is_required(self): response = self.api_session.post( '/@users', json={"password": "******"}, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue('Property \'username\' is required' in response.text) def test_add_user_password_is_required(self): response = self.api_session.post( '/@users', json={"username": "******"}, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue(('You have to either send a ' 'password or sendPasswordReset') in response.text) def test_add_user_email_is_required_if_email_login_is_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "username": "******", "password": "******" }, ) self.assertEqual(400, response.status_code) self.assertTrue( 'Property \'username\' is not allowed' in response.text) def test_add_user_email_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) self.assertTrue(api.user.get(userid='*****@*****.**')) def test_username_is_not_allowed_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue( 'Property \'username\' is not allowed' in response.text) def test_add_user_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) user = api.user.get(userid='*****@*****.**') self.assertTrue(user) self.assertEqual('*****@*****.**', user.getUserName()) self.assertEqual('*****@*****.**', user.getProperty('email')) def test_add_user_with_sendPasswordRest_sends_email(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "sendPasswordReset": True }, ) transaction.commit() self.assertEqual(201, response.status_code) self.assertTrue( 'To: [email protected]' in self.mailhost.messages[0]) def test_add_user_send_properties(self): response = self.api_session.post( '/@users', json={ "username": "******", "password": "******", "email": "*****@*****.**", "fullname": "Howard Zinn", }, ) transaction.commit() self.assertEqual(201, response.status_code) member = api.user.get(username='******') self.assertEqual(member.getProperty('fullname'), 'Howard Zinn') def test_add_anon_user_sends_properties_are_saved(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "fullname": "Howard Zinn", }, ) transaction.commit() self.assertEqual(201, response.status_code) member = api.user.get(username='******') self.assertEqual(member.getProperty('fullname'), 'Howard Zinn') def test_add_anon_no_roles(self): """Make sure anonymous users cannot set their own roles. Allowing so would make them Manager. """ security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "roles": ['Manager'], }, ) transaction.commit() self.assertEqual(400, response.status_code) errors = response.json()['error']['errors'] fields = [x['field'] for x in errors] self.assertEqual(['roles'], fields) def test_add_user_with_uuid_as_userid_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True security_settings.use_uuid_as_userid = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()['id'] user = api.user.get(userid=user_id) self.assertTrue(user) self.assertEqual('*****@*****.**', user.getUserName()) self.assertEqual('*****@*****.**', user.getProperty('email')) def test_get_user(self): response = self.api_session.get('/@users/noam') self.assertEqual(response.status_code, 200) self.assertEqual('noam', response.json().get('id')) self.assertEqual(self.portal.absolute_url() + '/@users/noam', response.json().get('@id')) self.assertEqual('*****@*****.**', response.json().get('email')) self.assertEqual('Noam Avram Chomsky', response.json().get('fullname')) self.assertEqual('web.mit.edu/chomsky', response.json().get('home_page')) # noqa self.assertEqual('Professor of Linguistics', response.json().get('description')) # noqa self.assertEqual('Cambridge, MA', response.json().get('location')) def test_get_user_as_anonymous(self): response = self.anon_api_session.get('/@users/noam') self.assertEqual(response.status_code, 401) def test_get_other_user_info_when_logged_in(self): noam_api_session = RelativeSession(self.portal_url) noam_api_session.headers.update({'Accept': 'application/json'}) noam_api_session.auth = ('noam', 'password') response = noam_api_session.get('/@users/otheruser') self.assertEqual(response.status_code, 401) noam_api_session.close() def test_get_search_user_with_filter(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() response = self.api_session.get('/@users', params={'query': 'noa'}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual('noam', response.json()[0].get('id')) self.assertEqual(self.portal.absolute_url() + '/@users/noam', response.json()[0].get('@id')) self.assertEqual('*****@*****.**', response.json()[0].get('email')) self.assertEqual('Noam Avram Chomsky', response.json()[0].get('fullname')) # noqa response = self.api_session.get('/@users', params={'query': 'howa'}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual('howard', response.json()[0].get('id')) def test_get_search_user_with_filter_as_anonymous(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() response = self.anon_api_session.get('/@users', params={'query': 'howa'}) self.assertEqual(response.status_code, 401) def test_get_search_user_with_filter_as_unauthorized_user(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() noam_api_session = RelativeSession(self.portal_url) noam_api_session.headers.update({'Accept': 'application/json'}) noam_api_session.auth = ('noam', 'password') response = noam_api_session.get('/@users', params={'query': 'howa'}) self.assertEqual(response.status_code, 401) noam_api_session.close() def test_get_non_existing_user(self): response = self.api_session.get('/@users/non-existing-user') self.assertEqual(response.status_code, 404) def test_update_user(self): payload = { 'fullname': 'Noam A. Chomsky', 'username': '******', 'email': '*****@*****.**' } response = self.api_session.patch('/@users/noam', json=payload) transaction.commit() self.assertEqual(response.status_code, 204) noam = api.user.get(userid='noam') self.assertEqual('noam', noam.getUserId()) # user id never changes self.assertEqual('avram', noam.getUserName()) self.assertEqual('Noam A. Chomsky', noam.getProperty('fullname')) self.assertEqual('*****@*****.**', noam.getProperty('email')) def test_user_can_update_himself(self): payload = { 'fullname': 'Noam A. Chomsky', 'username': '******', 'email': '*****@*****.**' } self.api_session.auth = ('noam', 'password') response = self.api_session.patch('/@users/noam', json=payload) self.assertEqual(response.status_code, 204) transaction.commit() noam = api.user.get(userid='noam') self.assertEqual('noam', noam.getUserId()) # user id never changes self.assertEqual('Noam A. Chomsky', noam.getProperty('fullname')) self.assertEqual('*****@*****.**', noam.getProperty('email')) def test_update_roles(self): self.assertNotIn('Contributor', api.user.get_roles(username='******')) self.api_session.patch('/@users/noam', json={'roles': { 'Contributor': True }}) transaction.commit() self.assertIn('Contributor', api.user.get_roles(username='******')) self.api_session.patch('/@users/noam', json={'roles': { 'Contributor': False }}) transaction.commit() self.assertNotIn('Contributor', api.user.get_roles(username='******')) def test_update_user_password(self): old_password_hashes = dict( self.portal.acl_users.source_users._user_passwords) payload = {'password': '******'} response = self.api_session.patch('/@users/noam', json=payload) transaction.commit() self.assertEqual(response.status_code, 204) new_password_hashes = dict( self.portal.acl_users.source_users._user_passwords) self.assertNotEqual(old_password_hashes['noam'], new_password_hashes['noam']) def test_anonymous_user_can_not_update_existing_user(self): payload = { 'fullname': 'Noam A. Chomsky', 'username': '******', 'email': '*****@*****.**' } self.api_session.auth = ('noam', 'password') response = self.anon_api_session.patch('/@users/noam', json=payload) self.assertEqual(response.status_code, 401) def test_user_can_not_update_another_user(self): payload = { 'fullname': 'Noam A. Chomsky', 'username': '******', 'email': '*****@*****.**' } self.api_session.auth = ('otheruser', 'otherpassword') response = self.api_session.patch('/@users/noam', json=payload) self.assertEqual(response.status_code, 403) def test_user_requests_password_sends_password_via_mail(self): self.api_session.auth = ('noam', 'password') payload = {} response = self.api_session.post('/@users/noam/reset-password', json=payload) transaction.commit() self.assertEqual(response.status_code, 200) # FIXME: Test that mail is sent def test_user_can_set_her_own_password(self): self.api_session.auth = ('noam', 'password') self.portal.manage_permission(SetOwnPassword, roles=['Authenticated', 'Manager'], acquire=False) transaction.commit() payload = {'old_password': '******', 'new_password': '******'} response = self.api_session.post('/@users/noam/reset-password', json=payload) transaction.commit() self.assertEqual(response.status_code, 200) authed = self.portal.acl_users.authenticate('noam', 'new_password', {}) self.assertTrue(authed) def test_normal_authenticated_user_cannot_set_other_users_password(self): self.api_session.auth = ('noam', 'password') self.portal.manage_permission(SetOwnPassword, roles=['Authenticated', 'Manager'], acquire=False) transaction.commit() payload = {'old_password': '******', 'new_password': '******'} response = self.api_session.post('/@users/otheruser/reset-password', json=payload) transaction.commit() self.assertEqual(response.status_code, 403) self.assertEqual(response.json()['error']['type'], 'Wrong user') def test_user_set_own_password_requires_set_own_password_permission(self): self.api_session.auth = ('noam', 'password') self.portal.manage_permission(SetOwnPassword, roles=['Manager'], acquire=False) transaction.commit() payload = {'old_password': '******', 'new_password': '******'} response = self.api_session.post('/@users/noam/reset-password', json=payload) transaction.commit() self.assertEqual(response.status_code, 403) def test_user_set_own_password_requires_old_and_new_password(self): self.api_session.auth = ('noam', 'password') payload = {'old_password': '******'} response = self.api_session.post('/@users/noam/reset-password', json=payload) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error']['type'], 'Invalid parameters') payload = {'new_password': '******'} response = self.api_session.post('/@users/noam/reset-password', json=payload) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error']['type'], 'Invalid parameters') def test_user_set_own_password_checks_old_password(self): self.api_session.auth = ('noam', 'password') payload = { 'new_password': '******', 'old_password': '******' } response = self.api_session.post('/@users/noam/reset-password', json=payload) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()['error']['type'], 'Wrong password') def test_user_set_reset_token_requires_new_password(self): self.api_session.auth = ('noam', 'password') payload = {'reset_token': 'abc'} response = self.api_session.post('/@users/noam/reset-password', json=payload) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error']['type'], 'Invalid parameters') def test_reset_with_token(self): reset_tool = getToolByName(self.portal, 'portal_password_reset') reset_info = reset_tool.requestReset('noam') token = reset_info['randomstring'] transaction.commit() payload = {'reset_token': token, 'new_password': '******'} response = self.api_session.post('/@users/noam/reset-password', json=payload) transaction.commit() self.assertEqual(response.status_code, 200) authed = self.portal.acl_users.authenticate('noam', 'new_password', {}) self.assertTrue(authed) def test_reset_with_uuid_as_userid_and_login_email_using_id(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True security_settings.use_uuid_as_userid = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()['id'] user = api.user.get(userid=user_id) self.assertTrue(user) reset_tool = getToolByName(self.portal, 'portal_password_reset') reset_info = reset_tool.requestReset(user.id) token = reset_info['randomstring'] transaction.commit() payload = {'reset_token': token, 'new_password': '******'} response = self.api_session.post('/@users/{}/reset-password'.format( user.id), json=payload) self.assertEqual(response.status_code, 200) def test_reset_with_uuid_as_userid_and_login_email_using_mail(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True security_settings.use_uuid_as_userid = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()['id'] user = api.user.get(userid=user_id) self.assertTrue(user) reset_tool = getToolByName(self.portal, 'portal_password_reset') reset_info = reset_tool.requestReset(user.id) token = reset_info['randomstring'] transaction.commit() payload = {'reset_token': token, 'new_password': '******'} response = self.api_session.post('/@users/{}/reset-password'.format( user.getUserName()), json=payload) self.assertEqual(response.status_code, 200) def test_reset_and_login_email_using_mail(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( '/@users', json={ "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()['id'] user = api.user.get(userid=user_id) self.assertTrue(user) reset_tool = getToolByName(self.portal, 'portal_password_reset') reset_info = reset_tool.requestReset(user.id) token = reset_info['randomstring'] transaction.commit() payload = {'reset_token': token, 'new_password': '******'} response = self.api_session.post('/@users/{}/reset-password'.format( user.getUserName()), json=payload) self.assertEqual(response.status_code, 200) def test_delete_user(self): response = self.api_session.delete('/@users/noam') transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(None, api.user.get(userid='noam')) def test_delete_non_existing_user(self): response = self.api_session.delete('/@users/non-existing-user') transaction.commit() self.assertEqual(response.status_code, 404) def test_anonymous_requires_enable_self_reg(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = False transaction.commit() response = self.anon_api_session.post( '/@users', json={"password": "******"}, ) transaction.commit() self.assertEqual(403, response.status_code) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", 'email': '*****@*****.**' }, ) transaction.commit() self.assertEqual(201, response.status_code) def test_anonymous_without_enable_user_pwd_choice_sends_mail(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", 'email': '*****@*****.**' }, ) transaction.commit() self.assertEqual(201, response.status_code) self.assertTrue( 'To: [email protected]' in self.mailhost.messages[0]) def test_anonymous_can_set_password_with_enable_user_pwd_choice(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", 'email': '*****@*****.**', 'password': '******' }, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue( 'Property \'password\' is not allowed' in response.text) security_settings.enable_user_pwd_choice = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", 'email': '*****@*****.**', 'password': '******' }, ) transaction.commit() self.assertEqual(201, response.status_code) def test_anonymous_with_enable_user_pwd_choice_doent_send_email(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True security_settings.enable_user_pwd_choice = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", 'email': '*****@*****.**', 'password': '******' }, ) transaction.commit() self.assertEqual(self.mailhost.messages, []) self.assertEqual(201, response.status_code) def test_anonymous_with_enable_user_sets_only_member_role(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True security_settings.enable_user_pwd_choice = True transaction.commit() response = self.anon_api_session.post( '/@users', json={ "username": "******", 'email': '*****@*****.**', 'password': '******' }, ) response = response.json() self.assertIn('Member', response['roles']) self.assertEqual(1, len(response['roles'])) def test_add_user_no_roles_sets_member_as_sensible_default(self): response = self.api_session.post( '/@users', json={ "username": "******", "email": "*****@*****.**", "password": "******" }, ) transaction.commit() self.assertEqual(201, response.status_code) response = response.json() self.assertIn('Member', response['roles']) self.assertEqual(1, len(response['roles']))
class TestCommentsEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.request = self.layer["request"] self.portal_url = self.portal.absolute_url() # Allow discussion registry = getUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.globally_enabled = True settings.edit_comment_enabled = True settings.delete_own_comment_enabled = True # doc with comments self.doc = api.content.create( container=self.portal, type="Document", id="doc_with_comments", title="Document with comments", allow_discussion=True, ) api.content.transition(self.doc, "publish") api.user.create(username="******", password="******", email="*****@*****.**") # Admin session self.api_session = RelativeSession(self.portal_url, test=self) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) # User session self.user_session = RelativeSession(self.portal_url, test=self) self.user_session.headers.update({"Accept": "application/json"}) self.user_session.auth = ("jos", "jos") transaction.commit() def tearDown(self): self.api_session.close() self.user_session.close() def test_list_datastructure(self): url = f"{self.doc.absolute_url()}/@comments" response = self.api_session.get(url) self.assertEqual(200, response.status_code) data = response.json() self.assertEqual({"items_total", "items", "@id"}, set(data)) def test_list_batching(self): url = f"{self.doc.absolute_url()}/@comments" self.api_session.post(url, json={"text": "comment 1"}) self.api_session.post(url, json={"text": "comment 2"}) response = self.api_session.get(url, params={"b_size": 1}) self.assertEqual(200, response.status_code) data = response.json() self.assertIn("batching", data) def test_add_comment_to_root(self): url = f"{self.doc.absolute_url()}/@comments" response = self.api_session.get(url) self.assertEqual(0, response.json()["items_total"]) response = self.api_session.post(url, json={"text": "comment 1"}) self.assertEqual(204, response.status_code) self.assertIn("location", response.headers) response = self.api_session.get(url) data = response.json() self.assertEqual(1, data["items_total"]) self.assertIsNone(data["items"][0]["in_reply_to"]) self.assertIsNone(data["items"][0]["@parent"]) def test_add_comment_to_comment(self): url = f"{self.doc.absolute_url()}/@comments" response = self.api_session.post(url, json={"text": "comment 1"}) self.assertEqual(204, response.status_code) response = self.api_session.get(url) data = response.json() parent_id = data["items"][0]["comment_id"] SUBTEXT = "sub comment" payload = {"text": SUBTEXT, "in_reply_to": parent_id} response = self.api_session.post(url, json=payload) self.assertEqual(204, response.status_code) response = self.api_session.get(url) data = response.json() sub = [x for x in data["items"] if x["text"]["data"] == SUBTEXT][0] self.assertEqual(parent_id, sub["in_reply_to"]) def test_update(self): url = f"{self.doc.absolute_url()}/@comments" OLD_TEXT = "comment 1" NEW_TEXT = "new text" self.api_session.post(url, json={"text": OLD_TEXT}) response = self.api_session.get(url) data = response.json() item_texts = [x["text"]["data"] for x in data["items"]] self.assertNotIn(NEW_TEXT, item_texts) self.assertIn(OLD_TEXT, item_texts) comment = data["items"][0] payload = {"text": NEW_TEXT} response = self.api_session.patch(comment["@id"], json=payload) self.assertEqual(204, response.status_code) self.assertIn("location", response.headers) response = self.api_session.get(url) data = response.json() item_texts = [x["text"]["data"] for x in data["items"]] self.assertIn(NEW_TEXT, item_texts) self.assertNotIn(OLD_TEXT, item_texts) def test_permissions_delete_comment(self): url = f"{self.doc.absolute_url()}/@comments" response = self.api_session.post(url, json={"text": "comment"}) self.assertEqual(204, response.status_code) response = self.api_session.get(url) comment_url = response.json()["items"][0]["@id"] self.assertFalse(comment_url.endswith("@comments")) self.assertTrue(response.json()["items"][0]["is_deletable"]) # Other user may not delete this response = self.user_session.delete(comment_url) self.assertEqual(401, response.status_code) response = self.user_session.get(url) self.assertFalse(response.json()["items"][0]["is_deletable"]) # The owner may response = self.api_session.delete(comment_url) self.assertEqual(204, response.status_code) def test_permissions_update_comment(self): url = f"{self.doc.absolute_url()}/@comments" response = self.api_session.post(url, json={"text": "comment"}) self.assertEqual(204, response.status_code) response = self.api_session.get(url) comment_url = response.json()["items"][0]["@id"] self.assertFalse(comment_url.endswith("@comments")) self.assertTrue(response.json()["items"][0]["is_editable"]) # Other user may not update this response = self.user_session.patch(comment_url, json={"text": "new"}) self.assertEqual(401, response.status_code) response = self.user_session.get(url) self.assertFalse(response.json()["items"][0]["is_editable"]) # The owner may response = self.api_session.patch(comment_url, json={"text": "new"}) self.assertEqual(204, response.status_code)
class TestUsersEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.mailhost = getUtility(IMailHost) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.anon_api_session = RelativeSession(self.portal_url) self.anon_api_session.headers.update({"Accept": "application/json"}) properties = { "email": "*****@*****.**", "username": "******", "fullname": "Noam Avram Chomsky", "home_page": "web.mit.edu/chomsky", "description": "Professor of Linguistics", "location": "Cambridge, MA", } api.user.create( email="*****@*****.**", username="******", properties=properties, password=u"password", ) properties = { "email": "*****@*****.**", "username": "******", "fullname": "Other user", } api.user.create( email="*****@*****.**", username="******", properties=properties, password=u"otherpassword", ) transaction.commit() def tearDown(self): self.api_session.close() self.anon_api_session.close() def test_list_users(self): response = self.api_session.get("/@users") self.assertEqual(200, response.status_code) self.assertEqual(4, len(response.json())) user_ids = [user["id"] for user in response.json()] self.assertIn("admin", user_ids) self.assertIn("test_user_1_", user_ids) self.assertIn("noam", user_ids) noam = [x for x in response.json() if x.get("username") == "noam"][0] self.assertEqual("noam", noam.get("id")) self.assertEqual(self.portal.absolute_url() + "/@users/noam", noam.get("@id")) self.assertEqual("*****@*****.**", noam.get("email")) self.assertEqual("Noam Avram Chomsky", noam.get("fullname")) self.assertEqual("web.mit.edu/chomsky", noam.get("home_page")) # noqa self.assertEqual("Professor of Linguistics", noam.get("description")) # noqa self.assertEqual("Cambridge, MA", noam.get("location")) def test_list_users_without_being_manager(self): noam_api_session = RelativeSession(self.portal_url) noam_api_session.headers.update({"Accept": "application/json"}) noam_api_session.auth = ("noam", "password") response = noam_api_session.get("/@users") self.assertEqual(response.status_code, 401) noam_api_session.close() def test_list_users_as_anonymous(self): response = self.anon_api_session.get("/@users") self.assertEqual(response.status_code, 401) def test_add_user(self): response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", "roles": ["Contributor"], }, ) transaction.commit() self.assertEqual(201, response.status_code) howard = api.user.get(userid="howard") self.assertEqual("*****@*****.**", howard.getProperty("email")) self.assertIn("Contributor", api.user.get_roles(username="******")) def test_add_user_username_is_required(self): response = self.api_session.post("/@users", json={"password": "******"}) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue("Property 'username' is required" in response.text) def test_add_user_password_is_required(self): response = self.api_session.post("/@users", json={"username": "******"}) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue( ("You have to either send a " "password or sendPasswordReset") in response.text ) def test_add_user_email_is_required_if_email_login_is_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( "/@users", json={"username": "******", "password": "******"} ) self.assertEqual(400, response.status_code) self.assertTrue("Property 'username' is not allowed" in response.text) def test_add_user_email_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( "/@users", json={"email": "*****@*****.**", "password": "******"} ) transaction.commit() self.assertEqual(201, response.status_code) self.assertTrue(api.user.get(userid="*****@*****.**")) def test_username_is_not_allowed_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue("Property 'username' is not allowed" in response.text) def test_add_user_with_email_login_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( "/@users", json={"email": "*****@*****.**", "password": "******"} ) transaction.commit() self.assertEqual(201, response.status_code) user = api.user.get(userid="*****@*****.**") self.assertTrue(user) self.assertEqual("*****@*****.**", user.getUserName()) self.assertEqual("*****@*****.**", user.getProperty("email")) def test_add_user_with_sendPasswordRest_sends_email(self): response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "sendPasswordReset": True, }, ) transaction.commit() self.assertEqual(201, response.status_code) msg = self.mailhost.messages[0] if isinstance(msg, bytes) and bytes is not str: # Python 3 with Products.MailHost 4.10+ msg = msg.decode("utf-8") self.assertTrue("To: [email protected]" in msg) def test_add_user_send_properties(self): response = self.api_session.post( "/@users", json={ "username": "******", "password": "******", "email": "*****@*****.**", "fullname": "Howard Zinn", }, ) transaction.commit() self.assertEqual(201, response.status_code) member = api.user.get(username="******") self.assertEqual(member.getProperty("fullname"), "Howard Zinn") def test_add_anon_user_sends_properties_are_saved(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "fullname": "Howard Zinn", }, ) transaction.commit() self.assertEqual(201, response.status_code) member = api.user.get(username="******") self.assertEqual(member.getProperty("fullname"), "Howard Zinn") def test_add_anon_no_roles(self): """Make sure anonymous users cannot set their own roles. Allowing so would make them Manager. """ security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "roles": ["Manager"], }, ) transaction.commit() self.assertEqual(400, response.status_code) errors = response.json()["error"]["errors"] fields = [x["field"] for x in errors] self.assertEqual(["roles"], fields) def test_add_user_with_uuid_as_userid_enabled(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True security_settings.use_uuid_as_userid = True transaction.commit() response = self.api_session.post( "/@users", json={"email": "*****@*****.**", "password": "******"} ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()["id"] user = api.user.get(userid=user_id) self.assertTrue(user) self.assertEqual("*****@*****.**", user.getUserName()) self.assertEqual("*****@*****.**", user.getProperty("email")) def test_get_user(self): response = self.api_session.get("/@users/noam") self.assertEqual(response.status_code, 200) self.assertEqual("noam", response.json().get("id")) self.assertEqual( self.portal.absolute_url() + "/@users/noam", response.json().get("@id") ) self.assertEqual("*****@*****.**", response.json().get("email")) self.assertEqual("Noam Avram Chomsky", response.json().get("fullname")) self.assertEqual( "web.mit.edu/chomsky", response.json().get("home_page") ) # noqa self.assertEqual( "Professor of Linguistics", response.json().get("description") ) # noqa self.assertEqual("Cambridge, MA", response.json().get("location")) def test_get_user_as_anonymous(self): response = self.anon_api_session.get("/@users/noam") self.assertEqual(response.status_code, 401) def test_get_other_user_info_when_logged_in(self): noam_api_session = RelativeSession(self.portal_url) noam_api_session.headers.update({"Accept": "application/json"}) noam_api_session.auth = ("noam", "password") response = noam_api_session.get("/@users/otheruser") self.assertEqual(response.status_code, 401) noam_api_session.close() def test_get_search_user_with_filter(self): response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() response = self.api_session.get("/@users", params={"query": "noa"}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual("noam", response.json()[0].get("id")) self.assertEqual( self.portal.absolute_url() + "/@users/noam", response.json()[0].get("@id") ) self.assertEqual("*****@*****.**", response.json()[0].get("email")) self.assertEqual( "Noam Avram Chomsky", response.json()[0].get("fullname") ) # noqa response = self.api_session.get("/@users", params={"query": "howa"}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual("howard", response.json()[0].get("id")) def test_get_search_user_with_filter_as_anonymous(self): response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() response = self.anon_api_session.get("/@users", params={"query": "howa"}) self.assertEqual(response.status_code, 401) def test_get_search_user_with_filter_as_unauthorized_user(self): response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() noam_api_session = RelativeSession(self.portal_url) noam_api_session.headers.update({"Accept": "application/json"}) noam_api_session.auth = ("noam", "password") response = noam_api_session.get("/@users", params={"query": "howa"}) self.assertEqual(response.status_code, 401) noam_api_session.close() def test_get_non_existing_user(self): response = self.api_session.get("/@users/non-existing-user") self.assertEqual(response.status_code, 404) def test_update_user(self): payload = { "fullname": "Noam A. Chomsky", "username": "******", "email": "*****@*****.**", } response = self.api_session.patch("/@users/noam", json=payload) transaction.commit() self.assertEqual(response.status_code, 204) noam = api.user.get(userid="noam") self.assertEqual("noam", noam.getUserId()) # user id never changes self.assertEqual("avram", noam.getUserName()) self.assertEqual("Noam A. Chomsky", noam.getProperty("fullname")) self.assertEqual("*****@*****.**", noam.getProperty("email")) def test_user_can_update_himself(self): payload = { "fullname": "Noam A. Chomsky", "username": "******", "email": "*****@*****.**", } self.api_session.auth = ("noam", "password") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() noam = api.user.get(userid="noam") self.assertEqual("noam", noam.getUserId()) # user id never changes self.assertEqual("Noam A. Chomsky", noam.getProperty("fullname")) self.assertEqual("*****@*****.**", noam.getProperty("email")) def test_user_can_update_himself_remove_values(self): payload = { "fullname": "Noam A. Chomsky", "username": "******", "email": "*****@*****.**", "home_page": None, } self.api_session.auth = ("noam", "password") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() noam = api.user.get(userid="noam") self.assertEqual(None, noam.getProperty("home_page")) def test_update_roles(self): self.assertNotIn("Contributor", api.user.get_roles(username="******")) self.api_session.patch("/@users/noam", json={"roles": {"Contributor": True}}) transaction.commit() self.assertIn("Contributor", api.user.get_roles(username="******")) self.api_session.patch("/@users/noam", json={"roles": {"Contributor": False}}) transaction.commit() self.assertNotIn("Contributor", api.user.get_roles(username="******")) def test_update_user_password(self): old_password_hashes = dict(self.portal.acl_users.source_users._user_passwords) payload = {"password": "******"} response = self.api_session.patch("/@users/noam", json=payload) transaction.commit() self.assertEqual(response.status_code, 204) new_password_hashes = dict(self.portal.acl_users.source_users._user_passwords) self.assertNotEqual(old_password_hashes["noam"], new_password_hashes["noam"]) def test_update_portrait(self): payload = { "portrait": { "filename": "image.gif", "encoding": "base64", "data": u"R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=", "content-type": "image/gif", } } self.api_session.auth = ("noam", "password") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() user = self.api_session.get("/@users/noam").json() self.assertTrue( user.get("portrait").endswith("plone/portal_memberdata/portraits/noam") ) def test_update_portrait_with_default_plone_scaling(self): payload = { "portrait": { "filename": "image.gif", "encoding": "base64", "data": u"R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=", "content-type": "image/gif", "scale": True, } } self.api_session.auth = ("noam", "password") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() user = self.api_session.get("/@users/noam").json() self.assertTrue( user.get("portrait").endswith("plone/portal_memberdata/portraits/noam") ) def test_update_portrait_by_manager(self): payload = { "portrait": { "filename": "image.gif", "encoding": "base64", "data": u"R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=", "content-type": "image/gif", } } response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() user = self.api_session.get("/@users/noam").json() self.assertTrue( user.get("portrait").endswith("plone/portal_memberdata/portraits/noam") ) def test_delete_portrait(self): payload = { "portrait": None, } self.api_session.auth = ("noam", "password") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() user = self.api_session.get("/@users/noam").json() self.assertTrue(user.get("portrait") is None) def test_delete_portrait_by_manager(self): payload = { "portrait": None, } response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() user = self.api_session.get("/@users/noam").json() self.assertTrue(user.get("portrait") is None) def test_update_user_with_portrait_set_without_updating_portrait(self): payload = { "portrait": { "filename": "image.gif", "encoding": "base64", "data": u"R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=", "content-type": "image/gif", } } self.api_session.auth = ("noam", "password") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() payload = { "fullname": "Noam A. Chomsky", "username": "******", "email": "*****@*****.**", "portrait": "http://*****:*****@users/noam", json=payload) self.assertEqual(response.status_code, 204) transaction.commit() user = self.api_session.get("/@users/noam").json() self.assertTrue( user.get("portrait").endswith("plone/portal_memberdata/portraits/noam") ) def test_anonymous_user_can_not_update_existing_user(self): payload = { "fullname": "Noam A. Chomsky", "username": "******", "email": "*****@*****.**", } self.api_session.auth = ("noam", "password") response = self.anon_api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 401) def test_user_can_not_update_another_user(self): payload = { "fullname": "Noam A. Chomsky", "username": "******", "email": "*****@*****.**", } self.api_session.auth = ("otheruser", "otherpassword") response = self.api_session.patch("/@users/noam", json=payload) self.assertEqual(response.status_code, 403) def test_user_requests_password_sends_password_via_mail(self): self.api_session.auth = ("noam", "password") payload = {} response = self.api_session.post("/@users/noam/reset-password", json=payload) transaction.commit() self.assertEqual(response.status_code, 200) # FIXME: Test that mail is sent def test_user_can_set_her_own_password(self): self.api_session.auth = ("noam", "password") self.portal.manage_permission( SetOwnPassword, roles=["Authenticated", "Manager"], acquire=False ) transaction.commit() payload = {"old_password": "******", "new_password": "******"} response = self.api_session.post("/@users/noam/reset-password", json=payload) transaction.commit() self.assertEqual(response.status_code, 200) authed = self.portal.acl_users.authenticate("noam", "new_password", {}) self.assertTrue(authed) def test_normal_authenticated_user_cannot_set_other_users_password(self): self.api_session.auth = ("noam", "password") self.portal.manage_permission( SetOwnPassword, roles=["Authenticated", "Manager"], acquire=False ) transaction.commit() payload = {"old_password": "******", "new_password": "******"} response = self.api_session.post( "/@users/otheruser/reset-password", json=payload ) transaction.commit() self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"]["type"], "Wrong user") def test_user_set_own_password_requires_set_own_password_permission(self): self.api_session.auth = ("noam", "password") self.portal.manage_permission(SetOwnPassword, roles=["Manager"], acquire=False) transaction.commit() payload = {"old_password": "******", "new_password": "******"} response = self.api_session.post("/@users/noam/reset-password", json=payload) transaction.commit() self.assertEqual(response.status_code, 403) def test_user_set_own_password_requires_old_and_new_password(self): self.api_session.auth = ("noam", "password") payload = {"old_password": "******"} response = self.api_session.post("/@users/noam/reset-password", json=payload) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"]["type"], "Invalid parameters") payload = {"new_password": "******"} response = self.api_session.post("/@users/noam/reset-password", json=payload) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"]["type"], "Invalid parameters") def test_user_set_own_password_checks_old_password(self): self.api_session.auth = ("noam", "password") payload = {"new_password": "******", "old_password": "******"} response = self.api_session.post("/@users/noam/reset-password", json=payload) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"]["type"], "Wrong password") def test_user_set_reset_token_requires_new_password(self): self.api_session.auth = ("noam", "password") payload = {"reset_token": "abc"} response = self.api_session.post("/@users/noam/reset-password", json=payload) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"]["type"], "Invalid parameters") def test_reset_with_token(self): reset_tool = getToolByName(self.portal, "portal_password_reset") reset_info = reset_tool.requestReset("noam") token = reset_info["randomstring"] transaction.commit() payload = {"reset_token": token, "new_password": "******"} response = self.api_session.post("/@users/noam/reset-password", json=payload) transaction.commit() self.assertEqual(response.status_code, 200) authed = self.portal.acl_users.authenticate("noam", "new_password", {}) self.assertTrue(authed) def test_reset_with_uuid_as_userid_and_login_email_using_id(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True security_settings.use_uuid_as_userid = True transaction.commit() response = self.api_session.post( "/@users", json={"email": "*****@*****.**", "password": "******"} ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()["id"] user = api.user.get(userid=user_id) self.assertTrue(user) reset_tool = getToolByName(self.portal, "portal_password_reset") reset_info = reset_tool.requestReset(user.id) token = reset_info["randomstring"] transaction.commit() payload = {"reset_token": token, "new_password": "******"} response = self.api_session.post( "/@users/{}/reset-password".format(user.id), json=payload ) self.assertEqual(response.status_code, 200) def test_reset_with_uuid_as_userid_and_login_email_using_mail(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True security_settings.use_uuid_as_userid = True transaction.commit() response = self.api_session.post( "/@users", json={"email": "*****@*****.**", "password": "******"} ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()["id"] user = api.user.get(userid=user_id) self.assertTrue(user) reset_tool = getToolByName(self.portal, "portal_password_reset") reset_info = reset_tool.requestReset(user.id) token = reset_info["randomstring"] transaction.commit() payload = {"reset_token": token, "new_password": "******"} response = self.api_session.post( "/@users/{}/reset-password".format(user.getUserName()), json=payload ) self.assertEqual(response.status_code, 200) def test_reset_and_login_email_using_mail(self): # enable use_email_as_login security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.use_email_as_login = True transaction.commit() response = self.api_session.post( "/@users", json={"email": "*****@*****.**", "password": "******"} ) transaction.commit() self.assertEqual(201, response.status_code) user_id = response.json()["id"] user = api.user.get(userid=user_id) self.assertTrue(user) reset_tool = getToolByName(self.portal, "portal_password_reset") reset_info = reset_tool.requestReset(user.id) token = reset_info["randomstring"] transaction.commit() payload = {"reset_token": token, "new_password": "******"} response = self.api_session.post( "/@users/{}/reset-password".format(user.getUserName()), json=payload ) self.assertEqual(response.status_code, 200) def test_delete_user(self): response = self.api_session.delete("/@users/noam") transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(None, api.user.get(userid="noam")) def test_delete_non_existing_user(self): response = self.api_session.delete("/@users/non-existing-user") transaction.commit() self.assertEqual(response.status_code, 404) def test_anonymous_requires_enable_self_reg(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = False transaction.commit() response = self.anon_api_session.post( "/@users", json={"password": "******"} ) transaction.commit() self.assertEqual(403, response.status_code) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( "/@users", json={"username": "******", "email": "*****@*****.**"}, ) transaction.commit() self.assertEqual(201, response.status_code) def test_anonymous_without_enable_user_pwd_choice_sends_mail(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( "/@users", json={"username": "******", "email": "*****@*****.**"}, ) transaction.commit() self.assertEqual(201, response.status_code) msg = self.mailhost.messages[0] if isinstance(msg, bytes) and bytes is not str: # Python 3 with Products.MailHost 4.10+ msg = msg.decode("utf-8") self.assertTrue("To: [email protected]" in msg) def test_anonymous_can_set_password_with_enable_user_pwd_choice(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True transaction.commit() response = self.anon_api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue("Property 'password' is not allowed" in response.text) security_settings.enable_user_pwd_choice = True transaction.commit() response = self.anon_api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() self.assertEqual(201, response.status_code) def test_anonymous_with_enable_user_pwd_choice_doent_send_email(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True security_settings.enable_user_pwd_choice = True transaction.commit() response = self.anon_api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() self.assertEqual(self.mailhost.messages, []) self.assertEqual(201, response.status_code) def test_anonymous_with_enable_user_sets_only_member_role(self): security_settings = getAdapter(self.portal, ISecuritySchema) security_settings.enable_self_reg = True security_settings.enable_user_pwd_choice = True transaction.commit() response = self.anon_api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) response = response.json() self.assertIn("Member", response["roles"]) self.assertEqual(1, len(response["roles"])) def test_add_user_no_roles_sets_member_as_sensible_default(self): response = self.api_session.post( "/@users", json={ "username": "******", "email": "*****@*****.**", "password": "******", }, ) transaction.commit() self.assertEqual(201, response.status_code) response = response.json() self.assertIn("Member", response["roles"]) self.assertEqual(1, len(response["roles"]))
class TestGroupsEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.request = self.layer["request"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.api_session = RelativeSession(self.portal_url, test=self) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.gtool = api.portal.get_tool("portal_groups") properties = { "title": "Plone Team", "description": "We are Plone", "email": "*****@*****.**", } self.gtool.addGroup( "ploneteam", (), (), properties=properties, title=properties["title"], description=properties["description"], ) transaction.commit() def tearDown(self): self.api_session.close() def test_list_groups(self): response = self.api_session.get("/@groups") self.assertEqual(200, response.status_code) self.assertEqual(5, len(response.json())) user_ids = [group["id"] for group in response.json()] self.assertIn("Administrators", user_ids) self.assertIn("Reviewers", user_ids) self.assertIn("AuthenticatedUsers", user_ids) self.assertIn("ploneteam", user_ids) ptgroup = [x for x in response.json() if x.get("groupname") == "ploneteam"][0] self.assertEqual("ploneteam", ptgroup.get("id")) self.assertEqual( self.portal.absolute_url() + "/@groups/ploneteam", ptgroup.get("@id") ) self.assertEqual("*****@*****.**", ptgroup.get("email")) self.assertEqual("Plone Team", ptgroup.get("title")) self.assertEqual("We are Plone", ptgroup.get("description")) self.assertEqual(ptgroup.get("roles"), ["Authenticated"]) # # Assert batched list of group members self.assertTrue( all(["members" in group for group in response.json()]), "Members key found in groups listing", ) def test_add_group(self): response = self.api_session.post( "/@groups", json={ "groupname": "fwt", "email": "*****@*****.**", "title": "Framework Team", "description": "The Plone Framework Team", "roles": ["Manager"], "groups": ["Administrators"], "users": [SITE_OWNER_NAME, TEST_USER_ID], }, ) transaction.commit() self.assertEqual(201, response.status_code) fwt = self.gtool.getGroupById("fwt") self.assertEqual("*****@*****.**", fwt.getProperty("email")) self.assertTrue( {SITE_OWNER_NAME, TEST_USER_ID}.issubset(set(fwt.getGroupMemberIds())), "Userids not found in group", ) def test_add_group_groupname_is_required(self): response = self.api_session.post("/@groups", json={"title": "Framework Team"}) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue("\"Property 'groupname' is required" in response.text) def test_get_group(self): response = self.api_session.get("/@groups/ploneteam") self.assertEqual(response.status_code, 200) self.assertEqual("ploneteam", response.json().get("id")) self.assertEqual( self.portal.absolute_url() + "/@groups/ploneteam", response.json().get("@id"), ) self.assertEqual("*****@*****.**", response.json().get("email")) self.assertEqual("*****@*****.**", response.json().get("email")) self.assertEqual("Plone Team", response.json().get("title")) self.assertEqual("We are Plone", response.json().get("description")) self.assertIn("members", response.json()) def test_get_search_group_with_filter(self): response = self.api_session.get("/@groups", params={"query": "plo"}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual("ploneteam", response.json()[0].get("id")) self.assertEqual( self.portal.absolute_url() + "/@groups/ploneteam", response.json()[0].get("@id"), ) self.assertEqual("*****@*****.**", response.json()[0].get("email")) response = self.api_session.get("/@groups", params={"query": "Auth"}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual("AuthenticatedUsers", response.json()[0].get("id")) def test_get_non_existing_group(self): response = self.api_session.get("/@groups/non-existing-group") self.assertEqual(response.status_code, 404) def test_update_group(self): ploneteam = self.gtool.getGroupById("ploneteam") ploneteam.addMember(SITE_OWNER_NAME) transaction.commit() self.assertNotIn(TEST_USER_ID, ploneteam.getGroupMemberIds()) self.assertIn(SITE_OWNER_NAME, ploneteam.getGroupMemberIds()) payload = { "groupname": "ploneteam", "email": "*****@*****.**", "users": {TEST_USER_ID: True, SITE_OWNER_NAME: False}, } response = self.api_session.patch("/@groups/ploneteam", json=payload) transaction.commit() self.assertEqual(response.status_code, 204) ploneteam = self.gtool.getGroupById("ploneteam") self.assertEqual("ploneteam", ploneteam.id) self.assertEqual("Plone Team", ploneteam.getProperty("title")) self.assertEqual("*****@*****.**", ploneteam.getProperty("email")) self.assertIn(TEST_USER_ID, ploneteam.getGroupMemberIds()) self.assertNotIn(SITE_OWNER_NAME, ploneteam.getGroupMemberIds()) def test_delete_group(self): response = self.api_session.delete("/@groups/ploneteam") transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(None, self.gtool.getGroupById("ploneteam")) def test_delete_non_existing_group(self): response = self.api_session.delete("/@groups/non-existing-group") transaction.commit() self.assertEqual(response.status_code, 404)
class TestPublicationFieldsFixes(unittest.TestCase): layer = REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING def setUp(self): tz = "Europe/Rome" os.environ["TZ"] = tz time.tzset() # Patch DateTime's timezone for deterministic behavior. self.DT_orig_localZone = DateTime.localZone DateTime.localZone = lambda cls=None, ltm=None: tz from plone.dexterity import content content.FLOOR_DATE = DateTime(1970, 0) content.CEILING_DATE = DateTime(2500, 0) self._orig_content_zone = content._zone content._zone = "GMT+2" registry = getUtility(IRegistry) registry["plone.portal_timezone"] = tz registry["plone.available_timezones"] = [tz] self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) commit() def tearDown(self): self.api_session.close() if "TZ" in os.environ: del os.environ["TZ"] time.tzset() from DateTime import DateTime DateTime.localZone = self.DT_orig_localZone from plone.dexterity import content content._zone = self._orig_content_zone content.FLOOR_DATE = DateTime(1970, 0) content.CEILING_DATE = DateTime(2500, 0) registry = getUtility(IRegistry) registry["plone.portal_timezone"] = "UTC" registry["plone.available_timezones"] = ["UTC"] def test_set_effective_date_store_right_value_in_plone(self): effective = DateTime() expires = effective + 1 self.api_session.post( self.portal_url, json={ "@type": "Document", "id": "mydocument", "title": "My Document", "effective": "{}Z".format(effective.utcdatetime().isoformat()), "expires": "{}Z".format(expires.utcdatetime().isoformat()), }, ) commit() self.assertEqual( self.portal["mydocument"].effective().strftime("%d-%m-%Y %H:%M"), effective.strftime("%d-%m-%Y %H:%M"), ) self.assertEqual( self.portal["mydocument"].expires().strftime("%d-%m-%Y %H:%M"), expires.strftime("%d-%m-%Y %H:%M"), )
class TestSearchFunctional(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() self.request = self.portal.REQUEST self.catalog = getToolByName(self.portal, "portal_catalog") self.api_session = RelativeSession(self.portal_url, test=self) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) api.user.create( email="*****@*****.**", username="******", password="******", ) api.user.create( email="*****@*****.**", username="******", password="******", ) # /plone/folder self.folder = createContentInContainer(self.portal, "Folder", id="folder", title="Some Folder") api.user.grant_roles(username="******", roles=["Editor"]) api.user.grant_roles(username="******", obj=self.folder, roles=["Editor", "Reader"]) # /plone/folder/doc self.doc = createContentInContainer( self.folder, "DXTestDocument", id="doc", title="Lorem Ipsum", start=DateTime(1950, 1, 1, 0, 0), effective=DateTime(1995, 1, 1, 0, 0), expires=DateTime(1999, 1, 1, 0, 0), test_int_field=42, test_list_field=["Keyword1", "Keyword2", "Keyword3"], test_bool_field=True, test_richtext_field=RichTextValue( raw="<p>Some Text</p>", mimeType="text/html", outputMimeType="text/html", ), ) IMutableUUID(self.doc).set("77779ffa110e45afb1ba502f75f77777") self.doc.reindexObject() # /plone/folder/other-document self.doc2 = createContentInContainer( self.folder, "DXTestDocument", id="other-document", title="Other Document", description="\xdcbersicht", start=DateTime(1975, 1, 1, 0, 0), effective=DateTime(2015, 1, 1, 0, 0), expires=DateTime(2020, 1, 1, 0, 0), test_list_field=["Keyword2", "Keyword3"], test_bool_field=False, ) # /plone/folder2 self.folder2 = createContentInContainer(self.portal, "Folder", id="folder2", title="Another Folder") # /plone/folder2/doc createContentInContainer( self.folder2, "DXTestDocument", id="doc", title="Document in second folder", start=DateTime(1975, 1, 1, 0, 0), effective=DateTime(2015, 1, 1, 0, 0), expires=DateTime(2020, 1, 1, 0, 0), test_bool_field=False, ) # /plone/doc-outside-folder createContentInContainer( self.portal, "DXTestDocument", id="doc-outside-folder", title="Doc outside folder", ) transaction.commit() def tearDown(self): self.api_session.close() def test_overall_response_format(self): response = self.api_session.get("/@search") self.assertEqual(response.status_code, 200) self.assertEqual(response.headers.get("Content-Type"), "application/json") results = response.json() self.assertEqual( results["items_total"], len(results["items"]), "items_total property should match actual item count.", ) def test_search_on_context_constrains_query_by_path(self): response = self.api_session.get("/folder/@search") self.assertSetEqual( { "/plone/folder", "/plone/folder/doc", "/plone/folder/other-document" }, set(result_paths(response.json())), ) def test_search_in_vhm(self): # Install a Virtual Host Monster if "virtual_hosting" not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster import ( manage_addVirtualHostMonster, ) manage_addVirtualHostMonster(self.app, "virtual_hosting") transaction.commit() # we don't get a result if we do not provide the full physical path response = self.api_session.get("/@search?path=/folder") self.assertSetEqual(set(), set(result_paths(response.json()))) # If we go through the VHM will will get results if we only use # the part of the path inside the VHM vhm_url = "{}/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/{}".format( self.app.absolute_url(), "@search?path=/folder", ) response = self.api_session.get(vhm_url) self.assertSetEqual( {"/folder", "/folder/doc", "/folder/other-document"}, set(result_paths(response.json())), ) def test_search_in_vhm_multiple_paths(self): # Install a Virtual Host Monster if "virtual_hosting" not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster import ( manage_addVirtualHostMonster, ) manage_addVirtualHostMonster(self.app, "virtual_hosting") transaction.commit() # path as a list query = {"path": ["/folder", "/folder2"]} # If we go through the VHM we will get results for multiple paths # if we only use the part of the path inside the VHM vhm_url = "{}/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/{}".format( self.app.absolute_url(), "@search", ) response = self.api_session.get(vhm_url, params=query) self.assertSetEqual( { "/folder", "/folder/doc", "/folder/other-document", "/folder2", "/folder2/doc", }, set(result_paths(response.json())), ) # path as a dict with a query list query = {"path.query": ["/folder", "/folder2"]} # If we go through the VHM we will get results for multiple paths # if we only use the part of the path inside the VHM vhm_url = "{}/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/{}".format( self.app.absolute_url(), "@search", ) response = self.api_session.get(vhm_url, params=query) self.assertSetEqual( { "/folder", "/folder/doc", "/folder/other-document", "/folder2", "/folder2/doc", }, set(result_paths(response.json())), ) def test_path_gets_prefilled_if_missing_from_path_query_dict(self): response = self.api_session.get("/@search?path.depth=1") self.assertSetEqual( {"/plone/folder", "/plone/folder2", "/plone/doc-outside-folder"}, set(result_paths(response.json())), ) def test_partial_metadata_retrieval(self): query = { "SearchableText": "lorem", "metadata_fields": ["portal_type", "review_state"], } response = self.api_session.get("/@search", params=query) self.assertDictContainsSubset( { "@id": self.portal_url + "/folder/doc", "title": "Lorem Ipsum", "portal_type": "DXTestDocument", "review_state": "private", }, response.json()["items"][0], ) def test_full_metadata_retrieval(self): query = {"SearchableText": "lorem", "metadata_fields": "_all"} response = self.api_session.get("/@search", params=query) first_item = response.json()["items"][0] self.assertLessEqual( { "@id": self.portal_url + "/folder/doc", "Creator": "test_user_1_", "Description": "", "EffectiveDate": "None", "ExpirationDate": "None", "Subject": [], "Title": "Lorem Ipsum", "Type": "DX Test Document", "UID": "77779ffa110e45afb1ba502f75f77777", "author_name": None, "cmf_uid": None, "commentators": [], "description": "", "effective": "1995-01-01T00:00:00+00:00", "end": None, "exclude_from_nav": False, "expires": "1999-01-01T00:00:00+00:00", "getId": "doc", "getPath": "/plone/folder/doc", "getRemoteUrl": None, "getURL": self.portal_url + "/folder/doc", "id": "doc", "in_response_to": None, "is_folderish": False, "last_comment_date": None, "listCreators": ["test_user_1_"], "location": None, "portal_type": "DXTestDocument", "review_state": "private", "start": "1950-01-01T00:00:00+00:00", "sync_uid": None, "title": "Lorem Ipsum", "total_comments": 0, }.items(), first_item.items(), ) # This value changed in Plone 5.2 # (Dexterity gained support for getObjSize) self.assertIn(first_item["getObjSize"], ("0 KB", "1 KB")) def test_full_objects_retrieval(self): query = { "SearchableText": "lorem", "metadata_fields": ["portal_type", "review_state"], "fullobjects": True, } response = self.api_session.get("/@search", params=query) self.assertEqual( "<p>Some Text</p>", response.json()["items"][0]["test_richtext_field"]["data"], ) self.assertEqual(self.portal_url + "/folder/doc", response.json()["items"][0]["@id"]) def test_full_objects_retrieval_discussion(self): # Allow discussion registry = getUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.globally_enabled = True self.doc.allow_discussion = True transaction.commit() url = f"{self.doc.absolute_url()}/@comments" self.api_session.post(url, json={"text": "comment 1"}) transaction.commit() query = {"portal_type": "Discussion Item", "fullobjects": True} response = self.api_session.get("/@search", params=query) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()["items"]), 1) def test_full_objects_retrieval_collections(self): self.collection = createContentInContainer(self.folder, "Collection", id="collection") transaction.commit() query = {"portal_type": "Collection", "fullobjects": True} response = self.api_session.get("/@search", params=query) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()["items"]), 1) def test_search_orphan_brain(self): # prevent unindex when deleting self.doc old__unindexObject = self.doc.__class__.unindexObject self.doc.__class__.unindexObject = lambda *args: None self.doc.aq_parent.manage_delObjects([self.doc.getId()]) self.doc.__class__.unindexObject = old__unindexObject # doc deleted but still in portal_catalog doc_uid = self.doc.UID() self.assertFalse(self.doc in self.doc.aq_parent) self.assertTrue(self.portal.portal_catalog(UID=doc_uid)) transaction.commit() # query with fullobjects query = { "portal_type": "DXTestDocument", "fullobjects": True, "UID": doc_uid } response = self.api_session.get("/@search", params=query) self.assertEqual(response.status_code, 200, response.content) results = response.json() self.assertEqual(len(results["items"]), 0) # query without fullobjects query = {"portal_type": "DXTestDocument", "UID": doc_uid} response = self.api_session.get("/@search", params=query) self.assertEqual(response.status_code, 200, response.content) results = response.json() self.assertEqual(len(results["items"]), 1) # ZCTextIndex def test_fulltext_search(self): query = {"SearchableText": "lorem"} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) def test_fulltext_search_with_non_ascii_characters(self): query = {"SearchableText": "\xfcbersicht"} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/other-document"], result_paths(response.json())) # KeywordIndex def test_keyword_index_str_query(self): query = {"test_list_field": "Keyword1"} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) def test_keyword_index_str_query_or(self): query = {"test_list_field": ["Keyword2", "Keyword3"]} response = self.api_session.get("/@search", params=query) self.assertEqual( ["/plone/folder/doc", "/plone/folder/other-document"], result_paths(response.json()), ) def test_keyword_index_str_query_and(self): query = { "test_list_field.query": ["Keyword1", "Keyword2"], "test_list_field.operator": "and", } response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) # BooleanIndex def test_boolean_index_query(self): query = {"test_bool_field": True, "portal_type": "DXTestDocument"} response = self.api_session.get("/folder/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) query = {"test_bool_field": False, "portal_type": "DXTestDocument"} response = self.api_session.get("/folder/@search", params=query) self.assertEqual(["/plone/folder/other-document"], result_paths(response.json())) # FieldIndex def test_field_index_int_query(self): query = {"test_int_field:int": 42} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) def test_field_index_int_range_query(self): query = { "test_int_field.query:int": [41, 43], "test_int_field.range": "min:max", } response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) # ExtendedPathIndex def test_extended_path_index_query(self): query = {"path": "/".join(self.folder.getPhysicalPath())} response = self.api_session.get("/@search", params=query) self.assertEqual( sorted([ "/plone/folder", "/plone/folder/doc", "/plone/folder/other-document", ]), sorted(result_paths(response.json())), ) def test_extended_path_index_query_multiple(self): # path as a list query = { "path": [ "/".join(self.folder.getPhysicalPath()), "/".join(self.folder2.getPhysicalPath()), ] } response = self.api_session.get("/@search", params=query) self.assertEqual( sorted([ "/plone/folder", "/plone/folder/doc", "/plone/folder/other-document", "/plone/folder2", "/plone/folder2/doc", ]), sorted(result_paths(response.json())), ) # path as a dict with a query list query = { "path.query": [ "/".join(self.folder.getPhysicalPath()), "/".join(self.folder2.getPhysicalPath()), ] } response = self.api_session.get("/@search", params=query) self.assertEqual( sorted([ "/plone/folder", "/plone/folder/doc", "/plone/folder/other-document", "/plone/folder2", "/plone/folder2/doc", ]), sorted(result_paths(response.json())), ) def test_extended_path_index_depth_limiting(self): lvl1 = createContentInContainer(self.portal, "Folder", id="lvl1") lvl2 = createContentInContainer(lvl1, "Folder", id="lvl2") createContentInContainer(lvl2, "Folder", id="lvl3") transaction.commit() path = "/plone/lvl1" # Depth 0 - only object identified by path query = {"path.query": path, "path.depth": 0} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/lvl1"], result_paths(response.json())) # Depth 1 - immediate children query = {"path.query": path, "path.depth": 1} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/lvl1/lvl2"], result_paths(response.json())) # No depth - object itself and all children query = {"path": path} response = self.api_session.get("/@search", params=query) self.assertSetEqual( {"/plone/lvl1", "/plone/lvl1/lvl2", "/plone/lvl1/lvl2/lvl3"}, set(result_paths(response.json())), ) # DateIndex def test_date_index_query(self): query = {"start": date(1950, 1, 1).isoformat()} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) def test_date_index_ranged_query(self): query = { "start.query": [date(1949, 1, 1).isoformat(), date(1951, 1, 1).isoformat()], "start.range": "min:max", } response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) # DateRangeIndex def test_date_range_index_query(self): query = {"effectiveRange": date(1997, 1, 1).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual(2, len(result_paths(response.json()))) self.assertTrue("/plone/folder" in result_paths(response.json())) self.assertTrue("/plone/folder/doc" in result_paths(response.json())) # DateRecurringIndex def test_date_recurring_index_query(self): from datetime import datetime createContentInContainer( self.folder, "Event", id="event", title="Event", start=datetime(2013, 1, 1, 0, 0), end=datetime(2013, 1, 1, 23, 59), whole_day=True, recurrence="FREQ=DAILY;COUNT=10;INTERVAL=2", timezone="UTC", ) import transaction transaction.commit() # First occurrence query = {"start": date(2013, 1, 1).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual(["/plone/folder/event"], result_paths(response.json())) # No event that day query = {"start": date(2013, 1, 2).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual([], result_paths(response.json())) # Second occurrence query = {"start": date(2013, 1, 3).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual(["/plone/folder/event"], result_paths(response.json())) # Ranged query query = { "start.query": [date(2013, 1, 1).isoformat(), date(2013, 1, 5).isoformat()], "start.range": "min:max", } response = self.api_session.get("/folder/@search", params=query) self.assertEqual(["/plone/folder/event"], result_paths(response.json())) # UUIDIndex def test_uuid_index_query(self): IMutableUUID(self.doc).set("7777a074cb4240d08c9a129e3a837777") self.doc.reindexObject() transaction.commit() query = {"UID": "7777a074cb4240d08c9a129e3a837777"} response = self.api_session.get("/@search", params=query) self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) def test_respect_access_inactive_permission(self): # admin can see everything response = self.api_session.get("/@search", params={}).json() if HAS_PLONE_6: # Since Plone 6 the Plone site is indexed ... self.assertEqual(response["items_total"], 7) else: # ... before it was not self.assertEqual(response["items_total"], 6) response = self.api_session.get("/@search", params={ "Title": "Lorem Ipsum" }).json() self.assertEqual(response["items_total"], 1) # not admin users can't see expired items self.api_session.auth = ("editoruser", "secret") response = self.api_session.get("/@search", params={}).json() if HAS_PLONE_6: # Since Plone 6 the Plone site is indexed ... self.assertEqual(response["items_total"], 4) else: # ... before it was not self.assertEqual(response["items_total"], 3) response = self.api_session.get("/@search", params={ "Title": "Lorem Ipsum" }).json() self.assertEqual(response["items_total"], 0) # now grant permission to Editor to access inactive content self.portal.manage_permission("Access inactive portal content", roles=["Manager", "Editor"]) transaction.commit() # portal-enabled Editor can see expired contents response = self.api_session.get("/@search", params={}).json() if HAS_PLONE_6: # Since Plone 6 the Plone site is indexed ... self.assertEqual(response["items_total"], 7) else: # ... before it was not self.assertEqual(response["items_total"], 6) response = self.api_session.get("/@search", params={ "Title": "Lorem Ipsum" }).json() self.assertEqual(response["items_total"], 1) # local-enabled Editor can only access expired contents inside folder self.api_session.auth = ("localeditor", "secret") response = self.api_session.get("/@search", params={}).json() if HAS_PLONE_6: # Since Plone 6 the Plone site is indexed ... self.assertEqual(response["items_total"], 2) else: # ... before it was not self.assertEqual(response["items_total"], 1) response = self.api_session.get("/@search", params={ "path": "/plone/folder" }).json() self.assertEqual(response["items_total"], 3) response = self.api_session.get("/@search", params={ "Title": "Lorem Ipsum" }).json() self.assertEqual(response["items_total"], 0) response = self.api_session.get( "/@search", params={ "Title": "Lorem Ipsum", "path": "/plone/folder" }, ).json() self.assertEqual(response["items_total"], 1) def test_search_use_site_search_settings_for_types(self): response = self.api_session.get("/@search", params={ "use_site_search_settings": 1 }).json() types = {item["@type"] for item in response["items"]} self.assertEqual(set(types), {"Folder", "DXTestDocument"}) registry = getUtility(IRegistry) from Products.CMFPlone.interfaces import ISearchSchema search_settings = registry.forInterface(ISearchSchema, prefix="plone") old = search_settings.types_not_searched search_settings.types_not_searched += ("DXTestDocument", ) transaction.commit() response = self.api_session.get("/@search", params={ "use_site_search_settings": 1 }).json() types = {item["@type"] for item in response["items"]} self.assertEqual(set(types), {"Folder"}) search_settings.types_not_searched = old transaction.commit() def test_search_use_site_search_settings_for_default_sort_order(self): response = self.api_session.get("/@search", params={ "use_site_search_settings": 1 }).json() titles = [ "Some Folder", "Lorem Ipsum", "Other Document", "Another Folder", "Document in second folder", "Doc outside folder", ] self.assertEqual([item["title"] for item in response["items"]], titles) response = self.api_session.get("/@search", params={ "use_site_search_settings": 1, "sort_on": "effective" }).json() self.assertEqual( [item["title"] for item in response["items"]][0], "Other Document", ) def test_search_use_site_search_settings_with_navigation_root(self): alsoProvides(self.folder, INavigationRoot) transaction.commit() response = self.api_session.get("/folder/@search", params={ "use_site_search_settings": 1 }).json() titles = ["Some Folder", "Lorem Ipsum", "Other Document"] self.assertEqual([item["title"] for item in response["items"]], titles) noLongerProvides(self.folder, INavigationRoot) transaction.commit() def test_search_use_site_search_settings_with_navigation_root_and_vhm( self): if "virtual_hosting" not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster import ( manage_addVirtualHostMonster, ) manage_addVirtualHostMonster(self.app, "virtual_hosting") alsoProvides(self.folder, INavigationRoot) transaction.commit() vhm_url = "{}/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/{}".format( self.app.absolute_url(), "/folder/@search", ) response = self.api_session.get(vhm_url, params={ "use_site_search_settings": 1, "path": "/folder" }).json() titles = ["Some Folder", "Lorem Ipsum", "Other Document"] self.assertEqual([item["title"] for item in response["items"]], titles) noLongerProvides(self.folder, INavigationRoot) transaction.commit() def test_search_use_site_search_settings_with_vhm(self): if "virtual_hosting" not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster import ( manage_addVirtualHostMonster, ) manage_addVirtualHostMonster(self.app, "virtual_hosting") transaction.commit() vhm_url = "{}/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/{}".format( self.app.absolute_url(), "/@search", ) response = self.api_session.get(vhm_url, params={ "use_site_search_settings": 1, "path": "/" }).json() titles = sorted([ "Another Folder", "Doc outside folder", "Document in second folder", "Lorem Ipsum", "Other Document", "Some Folder", ]) self.assertEqual(sorted([item["title"] for item in response["items"]]), titles) noLongerProvides(self.folder, INavigationRoot) transaction.commit()
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)
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)
class TestCommentsEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.request = self.layer['request'] self.portal_url = self.portal.absolute_url() # Allow discussion registry = getUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.globally_enabled = True settings.edit_comment_enabled = True settings.delete_own_comment_enabled = True # doc with comments self.doc = api.content.create(container=self.portal, type='Document', id='doc_with_comments', title='Document with comments', allow_discussion=True) api.content.transition(self.doc, 'publish') api.user.create(username='******', password='******', email='*****@*****.**') # Admin session self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) # User session self.user_session = RelativeSession(self.portal_url) self.user_session.headers.update({'Accept': 'application/json'}) self.user_session.auth = ('jos', 'jos') transaction.commit() def test_list_datastructure(self): url = '{}/@comments'.format(self.doc.absolute_url()) response = self.api_session.get(url) self.assertEqual(200, response.status_code) data = response.json() self.assertEqual(set(['items_total', 'items', '@id']), set(data)) def test_list_batching(self): url = '{}/@comments'.format(self.doc.absolute_url()) self.api_session.post(url, json={'text': 'comment 1'}) self.api_session.post(url, json={'text': 'comment 2'}) response = self.api_session.get(url, params={'b_size': 1}) self.assertEqual(200, response.status_code) data = response.json() self.assertIn('batching', data) def test_add_comment_to_root(self): url = '{}/@comments'.format(self.doc.absolute_url()) response = self.api_session.get(url) self.assertEqual(0, response.json()['items_total']) response = self.api_session.post(url, json={'text': 'comment 1'}) self.assertEqual(204, response.status_code) self.assertIn('location', response.headers) response = self.api_session.get(url) data = response.json() self.assertEqual(1, data['items_total']) self.assertIsNone(data['items'][0]['in_reply_to']) self.assertIsNone(data['items'][0]['@parent']) def test_add_comment_to_comment(self): url = '{}/@comments'.format(self.doc.absolute_url()) response = self.api_session.post(url, json={'text': 'comment 1'}) self.assertEqual(204, response.status_code) response = self.api_session.get(url) data = response.json() parent_id = data['items'][0]['comment_id'] SUBTEXT = 'sub comment' payload = {'text': SUBTEXT, 'in_reply_to': parent_id} response = self.api_session.post(url, json=payload) self.assertEqual(204, response.status_code) response = self.api_session.get(url) data = response.json() sub = [x for x in data['items'] if x['text']['data'] == SUBTEXT][0] self.assertEqual(parent_id, sub['in_reply_to']) def test_update(self): url = '{}/@comments'.format(self.doc.absolute_url()) OLD_TEXT = 'comment 1' NEW_TEXT = 'new text' self.api_session.post(url, json={'text': OLD_TEXT}) response = self.api_session.get(url) data = response.json() item_texts = [x['text']['data'] for x in data['items']] self.assertNotIn(NEW_TEXT, item_texts) self.assertIn(OLD_TEXT, item_texts) comment = data['items'][0] payload = {'text': NEW_TEXT} response = self.api_session.patch(comment['@id'], json=payload) self.assertEqual(204, response.status_code) self.assertIn('location', response.headers) response = self.api_session.get(url) data = response.json() item_texts = [x['text']['data'] for x in data['items']] self.assertIn(NEW_TEXT, item_texts) self.assertNotIn(OLD_TEXT, item_texts) def test_permissions_delete_comment(self): url = '{}/@comments'.format(self.doc.absolute_url()) response = self.api_session.post(url, json={'text': 'comment'}) self.assertEqual(204, response.status_code) response = self.api_session.get(url) comment_url = response.json()['items'][0]['@id'] self.assertFalse(comment_url.endswith('@comments')) self.assertTrue(response.json()['items'][0]['is_deletable']) # Other user may not delete this response = self.user_session.delete(comment_url) self.assertEqual(401, response.status_code) response = self.user_session.get(url) self.assertFalse(response.json()['items'][0]['is_deletable']) # The owner may response = self.api_session.delete(comment_url) self.assertEqual(204, response.status_code) def test_permissions_update_comment(self): url = '{}/@comments'.format(self.doc.absolute_url()) response = self.api_session.post(url, json={'text': 'comment'}) self.assertEqual(204, response.status_code) response = self.api_session.get(url) comment_url = response.json()['items'][0]['@id'] self.assertFalse(comment_url.endswith('@comments')) self.assertTrue(response.json()['items'][0]['is_editable']) # Other user may not update this response = self.user_session.patch(comment_url, json={'text': 'new'}) self.assertEqual(401, response.status_code) response = self.user_session.get(url) self.assertFalse(response.json()['items'][0]['is_editable']) # The owner may response = self.api_session.patch(comment_url, json={'text': 'new'}) self.assertEqual(204, response.status_code)
class TestTraversal(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.request = self.layer['request'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() self.time_freezer = freeze_time("2016-10-21 19:00:00") self.frozen_time = self.time_freezer.start() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) setRoles(self.portal, TEST_USER_ID, ['Manager']) self.portal.invokeFactory('Document', id='front-page') self.document = self.portal['front-page'] self.document.title = u"Welcome to Plone" self.document.description = \ u"Congratulations! You have successfully installed Plone." self.document.text = RichTextValue( u"If you're seeing this instead of the web site you were " + u"expecting, the owner of this web site has just installed " + u"Plone. Do not contact the Plone Team or the Plone mailing " + u"lists about this.", 'text/plain', 'text/html' ) self.document.creation_date = DateTime('2016-01-21T01:14:48+00:00') IMutableUUID(self.document).set('1f699ffa110e45afb1ba502f75f7ec33') self.document.reindexObject() self.document.modification_date = DateTime('2016-01-21T01:24:11+00:00') import transaction transaction.commit() self.browser = Browser(self.app) self.browser.handleErrors = False self.browser.addHeader( 'Authorization', 'Basic %s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,) ) def tearDown(self): self.time_freezer.stop() def test_documentation_document(self): response = self.api_session.get(self.document.absolute_url()) save_response_for_documentation('document.json', response) def test_documentation_news_item(self): self.portal.invokeFactory('News Item', id='newsitem') self.portal.newsitem.title = 'My News Item' self.portal.newsitem.description = u'This is a news item' self.portal.newsitem.text = RichTextValue( u"Lorem ipsum", 'text/plain', 'text/html' ) image_file = os.path.join(os.path.dirname(__file__), u'image.png') self.portal.newsitem.image = NamedBlobImage( data=open(image_file, 'r').read(), contentType='image/png', filename=u'image.png' ) self.portal.newsitem.image_caption = u'This is an image caption.' self.portal.newsitem.creation_date = DateTime( '2016-01-21T02:14:48+00:00') self.portal.newsitem.modification_date = DateTime( '2016-01-21T02:24:11+00:00') IMutableUUID(self.portal.newsitem).set( '80c2a074cb4240d08c9a129e3a834c74') import transaction transaction.commit() response = self.api_session.get(self.portal.newsitem.absolute_url()) save_response_for_documentation('newsitem.json', response) def test_documentation_event(self): self.portal.invokeFactory('Event', id='event') self.portal.event.title = 'Event' self.portal.event.description = u'This is an event' self.portal.event.start = datetime(2013, 1, 1, 10, 0) self.portal.event.end = datetime(2013, 1, 1, 12, 0) self.portal.event.creation_date = DateTime('2016-01-21T03:14:48+00:00') self.portal.event.modification_date = DateTime( '2016-01-21T03:24:11+00:00') IMutableUUID(self.portal.event).set('846d632bc0854c5aa6d3dcae171ed2db') import transaction transaction.commit() response = self.api_session.get(self.portal.event.absolute_url()) save_response_for_documentation('event.json', response) def test_documentation_link(self): self.portal.invokeFactory('Link', id='link') self.portal.link.title = 'My Link' self.portal.link.description = u'This is a link' self.portal.remoteUrl = 'http://plone.org' self.portal.link.creation_date = DateTime('2016-01-21T04:14:48+00:00') self.portal.link.modification_date = DateTime( '2016-01-21T04:24:11+00:00') IMutableUUID(self.portal.link).set('6ff48d27762143a0ae8d63cee73d9fc2') import transaction transaction.commit() response = self.api_session.get(self.portal.link.absolute_url()) save_response_for_documentation('link.json', response) def test_documentation_file(self): self.portal.invokeFactory('File', id='file') self.portal.file.title = 'My File' self.portal.file.description = u'This is a file' pdf_file = os.path.join( os.path.dirname(__file__), u'file.pdf' ) self.portal.file.file = NamedBlobFile( data=open(pdf_file, 'r').read(), contentType='application/pdf', filename=u'file.pdf' ) self.portal.file.creation_date = DateTime('2016-01-21T05:14:48+00:00') self.portal.file.modification_date = DateTime( '2016-01-21T05:24:11+00:00') IMutableUUID(self.portal.file).set('9b6a4eadb9074dde97d86171bb332ae9') import transaction transaction.commit() response = self.api_session.get(self.portal.file.absolute_url()) save_response_for_documentation('file.json', response) def test_documentation_image(self): self.portal.invokeFactory('Image', id='image') self.portal.image.title = 'My Image' self.portal.image.description = u'This is an image' image_file = os.path.join(os.path.dirname(__file__), u'image.png') self.portal.image.image = NamedBlobImage( data=open(image_file, 'r').read(), contentType='image/png', filename=u'image.png' ) self.portal.image.creation_date = DateTime('2016-01-21T06:14:48+00:00') self.portal.image.modification_date = DateTime( '2016-01-21T06:24:11+00:00') IMutableUUID(self.portal.image).set('2166e81a0c224fe3b62e197c7fdc9c3e') import transaction transaction.commit() response = self.api_session.get(self.portal.image.absolute_url()) save_response_for_documentation('image.json', response) def test_documentation_folder(self): self.portal.invokeFactory('Folder', id='folder') self.portal.folder.title = 'My Folder' self.portal.folder.description = u'This is a folder with two documents' self.portal.folder.invokeFactory( 'Document', id='doc1', title='A document within a folder' ) self.portal.folder.invokeFactory( 'Document', id='doc2', title='A document within a folder' ) self.portal.folder.creation_date = DateTime( '2016-01-21T07:14:48+00:00') self.portal.folder.modification_date = DateTime( '2016-01-21T07:24:11+00:00') IMutableUUID(self.portal.folder).set( 'fc7881c46d61452db4177bc059d9dcb5') import transaction transaction.commit() response = self.api_session.get(self.portal.folder.absolute_url()) save_response_for_documentation('folder.json', response) def test_documentation_collection(self): self.portal.invokeFactory('Collection', id='collection') self.portal.collection.title = 'My Collection' self.portal.collection.description = \ u'This is a collection with two documents' self.portal.collection.query = [{ 'i': 'portal_type', 'o': 'plone.app.querystring.operation.string.is', 'v': 'Document', }] self.portal.invokeFactory( 'Document', id='doc1', title='Document 1' ) self.portal.invokeFactory( 'Document', id='doc2', title='Document 2' ) self.portal.collection.creation_date = DateTime( '2016-01-21T08:14:48+00:00') self.portal.collection.modification_date = DateTime( '2016-01-21T08:24:11+00:00') IMutableUUID(self.portal.collection).set( 'd0c89bc77f874ce1aad5720921d875c0') import transaction transaction.commit() response = self.api_session.get(self.portal.collection.absolute_url()) save_response_for_documentation('collection.json', response) def test_documentation_siteroot(self): response = self.api_session.get(self.portal.absolute_url()) save_response_for_documentation('siteroot.json', response) def test_documentation_404_not_found(self): response = self.api_session.get('non-existing-resource') save_response_for_documentation('404_not_found.json', response) def test_documentation_search(self): query = {'sort_on': 'path'} response = self.api_session.get('/@search', params=query) save_response_for_documentation('search.json', response) def test_documentation_workflow(self): response = self.api_session.get( '{}/@workflow'.format(self.document.absolute_url())) save_response_for_documentation('workflow_get.json', response) def test_documentation_workflow_transition(self): self.frozen_time.tick(timedelta(minutes=5)) response = self.api_session.post( '{}/@workflow/publish'.format(self.document.absolute_url())) save_response_for_documentation('workflow_post.json', response) def test_documentation_registry_get(self): response = self.api_session.get( '/@registry/plone.app.querystring.field.path.title') save_response_for_documentation('registry_get.json', response) def test_documentation_registry_update(self): response = self.api_session.patch( '/@registry/', json={'plone.app.querystring.field.path.title': 'Value'}) save_response_for_documentation('registry_update.json', response) def test_documentation_types(self): response = self.api_session.get('/@types') save_response_for_documentation('types.json', response) def test_documentation_types_document(self): response = self.api_session.get('@types/Document') save_response_for_documentation('types_document.json', response) def test_documentation_login(self): self.portal.acl_users.jwt_auth._secret = 'secret' self.portal.acl_users.jwt_auth.use_keyring = False self.portal.acl_users.jwt_auth.token_timeout = 0 import transaction transaction.commit() self.api_session.auth = None response = self.api_session.post( '{}/@login'.format(self.portal.absolute_url()), json={'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD}) save_response_for_documentation('login.json', response) def test_documentation_login_renew(self): self.portal.acl_users.jwt_auth._secret = 'secret' self.portal.acl_users.jwt_auth.use_keyring = False self.portal.acl_users.jwt_auth.token_timeout = 0 import transaction transaction.commit() self.api_session.auth = None response = self.api_session.post( '{}/@login'.format(self.portal.absolute_url()), json={'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD}) token = json.loads(response.content)['token'] response = self.api_session.post( '{}/@login-renew'.format(self.portal.absolute_url()), headers={'Authorization': 'Bearer {}'.format(token)}) save_response_for_documentation('login_renew.json', response) def test_documentation_logout(self): self.portal.acl_users.jwt_auth._secret = 'secret' self.portal.acl_users.jwt_auth.use_keyring = False self.portal.acl_users.jwt_auth.token_timeout = 0 self.portal.acl_users.jwt_auth.store_tokens = True import transaction transaction.commit() self.api_session.auth = None response = self.api_session.post( '{}/@login'.format(self.portal.absolute_url()), json={'login': SITE_OWNER_NAME, 'password': SITE_OWNER_PASSWORD}) token = json.loads(response.content)['token'] response = self.api_session.post( '{}/@logout'.format(self.portal.absolute_url()), headers={'Authorization': 'Bearer {}'.format(token)}) save_response_for_documentation('logout.json', response) def test_documentation_batching(self): folder = self.portal[self.portal.invokeFactory( 'Folder', id='folder', title='Folder' )] for i in range(7): folder.invokeFactory( 'Document', id='doc-%s' % str(i + 1), title='Document %s' % str(i + 1) ) transaction.commit() query = {'sort_on': 'path'} response = self.api_session.get( '/folder/@search?b_size=5', params=query) save_response_for_documentation('batching.json', response) def test_documentation_users(self): test_user = api.user.get(username=TEST_USER_ID) properties = { "description": "This is a test user", "email": "*****@*****.**", "fullname": "Test User", "home_page": "http://www.example.com", "location": "Bonn", "username": "******" } test_user.setMemberProperties(mapping=properties) admin = api.user.get(username='******') properties = { "description": "This is an admin user", "email": "*****@*****.**", "fullname": "Administrator", "home_page": "http://www.example.com", "location": "Berlin", "username": "******" } admin.setMemberProperties(mapping=properties) transaction.commit() response = self.api_session.get('/@users') save_response_for_documentation('users.json', response) def test_documentation_users_get(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create( email='*****@*****.**', username='******', properties=properties ) transaction.commit() response = self.api_session.get('@users/noam') save_response_for_documentation('users_get.json', response) def test_documentation_users_filtered_get(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create( email='*****@*****.**', username='******', properties=properties ) transaction.commit() response = self.api_session.get('@users', params={'query': 'noa'}) save_response_for_documentation('users_filtered_by_username.json', response) # noqa def test_documentation_users_created(self): response = self.api_session.post( '/@users', json={ 'username': '******', 'email': '*****@*****.**', 'password': '******', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' }, ) save_response_for_documentation('users_created.json', response) def test_documentation_users_update(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create( email='*****@*****.**', username='******', properties=properties ) transaction.commit() response = self.api_session.patch( '/@users/noam', json={ 'email': '*****@*****.**', }, ) save_response_for_documentation('users_update.json', response) def test_documentation_users_delete(self): properties = { 'email': '*****@*****.**', 'username': '******', 'fullname': 'Noam Avram Chomsky', 'home_page': 'web.mit.edu/chomsky', 'description': 'Professor of Linguistics', 'location': 'Cambridge, MA' } api.user.create( email='*****@*****.**', username='******', properties=properties ) transaction.commit() response = self.api_session.delete( '/@users/noam') save_response_for_documentation('users_delete.json', response) def test_documentation_breadcrumbs(self): response = self.api_session.get( '{}/@components/breadcrumbs'.format(self.document.absolute_url())) save_response_for_documentation('breadcrumbs.json', response) def test_documentation_navigation(self): response = self.api_session.get( '{}/@components/navigation'.format(self.document.absolute_url())) save_response_for_documentation('navigation.json', response)
class TestCopyMoveFunctional(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.doc1 = self.portal[self.portal.invokeFactory("Document", id="doc1", title="My Document")] self.doc2 = self.portal[self.portal.invokeFactory("Document", id="doc2", title="My Document")] self.folder1 = self.portal[self.portal.invokeFactory( "Folder", id="folder1", title="My Folder")] api.user.create(email="*****@*****.**", username="******", password="******") self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) transaction.commit() def tearDown(self): self.api_session.close() def test_copy_single_object(self): response = self.api_session.post( "/@copy", json={"source": self.doc1.absolute_url()}) transaction.commit() self.assertEqual(response.status_code, 200) self.assertIn("copy_of_doc1", self.portal.objectIds()) def test_move_single_object(self): response = self.api_session.post( "/folder1/@move", json={"source": self.doc1.absolute_url()}) transaction.commit() self.assertEqual(response.status_code, 200) self.assertIn("doc1", self.folder1.objectIds()) self.assertNotIn("doc1", self.portal.objectIds()) def test_move_multiple_objects(self): response = self.api_session.post( "/folder1/@move", json={ "source": [self.doc1.absolute_url(), self.doc2.absolute_url()] }, ) self.assertEqual(response.status_code, 200) transaction.commit() self.assertIn("doc1", self.folder1.objectIds()) self.assertIn("doc2", self.folder1.objectIds()) self.assertNotIn("doc1", self.portal.objectIds()) self.assertNotIn("doc2", self.portal.objectIds()) def test_copy_without_source_raises_400(self): response = self.api_session.post("/folder1/@copy") self.assertEqual(response.status_code, 400) def test_copy_not_existing_object(self): response = self.api_session.post("/@copy", json={"source": "does-not-exist"}) self.assertEqual(response.status_code, 200) self.assertEqual([], response.json()) def test_copy_multiple_objects(self): response = self.api_session.post( "/@copy", json={ "source": [self.doc1.absolute_url(), self.doc2.absolute_url()] }, ) transaction.commit() self.assertEqual(response.status_code, 200) self.assertIn("copy_of_doc1", self.portal.objectIds()) self.assertIn("copy_of_doc2", self.portal.objectIds()) def test_copy_single_object_no_permissions_raises_403(self): self.api_session.auth = ("memberuser", "secret") response = self.api_session.post( "/@copy", json={"source": self.doc1.absolute_url()}) self.assertEqual(response.status_code, 403) def test_copy_single_object_no_auth_raises_401(self): self.api_session.auth = ("nonexistent", "secret") response = self.api_session.post( "/@copy", json={"source": self.doc1.absolute_url()}) self.assertEqual(response.status_code, 401) def test_move_single_object_no_permissions_raises_403(self): self.api_session.auth = ("memberuser", "secret") response = self.api_session.post( "/@move", json={"source": self.doc1.absolute_url()}) self.assertEqual(response.status_code, 403) def test_move_single_object_no_auth_raises_401(self): self.api_session.auth = ("nonexistent", "secret") response = self.api_session.post( "/@move", json={"source": self.doc1.absolute_url()}) self.assertEqual(response.status_code, 401) def test_move_single_object_no_permission_delete_source_raises_403(self): api.user.grant_roles(username="******", obj=self.folder1, roles=["Manager"]) api.content.transition(obj=self.doc1, transition="publish") transaction.commit() self.api_session.auth = ("memberuser", "secret") response = self.api_session.post( "/folder1/@move", json={"source": self.doc1.absolute_url()}) self.assertEqual(response.status_code, 403)
class DeserializerFunctionalTest(unittest.TestCase): """ """ layer = PLONE_APP_JSON_FIELD_FUNCTIONAL_TESTING def setUp(self): """ """ self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) def test_deserializer(self): """ """ json_body = { '@type': 'TestToken', 'title': 'Test Organization xxx', 'id': 'test-hospital' } with open(os.path.join(FHIR_FIXTURE_PATH, 'Organization.json'), 'r') as f: json_body['resource'] = json.load(f) response = self.api_session.post( self.portal_url, json=json_body, ) self.assertEqual(201, response.status_code) self.assertEqual(response.json()['resource']['resourceType'], json_body['resource']['resourceType']) # Test with mismatched fhir json, I mean mismatcged resource json_body['resource'] = """{ "resourceType": "ValueSet", "id": "yesnodontknow", "url": "http://hl7.org/fhir/ValueSet/yesnodontknow", "name": "Yes/No/Don't Know", "status": "draft", "description": "For Capturing simple yes-no-don't know answers", "compose": { "include": [ { "valueSet": [ "http://hl7.org/fhir/ValueSet/v2-0136" ] }, { "system": "http://hl7.org/fhir/data-absent-reason", "concept": [ { "code": "asked", "display": "Don't know" } ] } ] }, "expansion": { "identifier": "urn:uuid:bf99fe50-2c2b-41ad-bd63-bee6919810b4", "timestamp": "2015-07-14T10:00:00Z", "contains": [ { "system": "http://hl7.org/fhir/v2/0136", "code": "Y", "display": "Yes" }, { "system": "http://hl7.org/fhir/v2/0136", "code": "N", "display": "No" }, { "system": "http://hl7.org/fhir/data-absent-reason", "code": "asked", "display": "Don't know" } ] } } """ json_body['resource'] = json.loads(json_body['resource']) json_body['id'] = 'another-hospital' response = self.api_session.post( self.portal_url, json=json_body, )
class TestCopyMoveFunctional(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Manager']) self.doc1 = self.portal[self.portal.invokeFactory('Document', id='doc1', title='My Document')] self.doc2 = self.portal[self.portal.invokeFactory('Document', id='doc2', title='My Document')] self.folder1 = self.portal[self.portal.invokeFactory( 'Folder', id='folder1', title='My Folder')] api.user.create(email='*****@*****.**', username='******', password='******') self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) transaction.commit() def test_copy_single_object(self): response = self.api_session.post( '/@copy', json={"source": self.doc1.absolute_url()}) transaction.commit() self.assertEquals(response.status_code, 200) self.assertIn('copy_of_doc1', self.portal.objectIds()) def test_move_single_object(self): response = self.api_session.post( '/folder1/@move', json={"source": self.doc1.absolute_url()}) transaction.commit() self.assertEquals(response.status_code, 200) self.assertIn('doc1', self.folder1.objectIds()) self.assertNotIn('doc1', self.portal.objectIds()) def test_move_multiple_objects(self): response = self.api_session.post( '/folder1/@move', json={ "source": [self.doc1.absolute_url(), self.doc2.absolute_url()] }) self.assertEquals(response.status_code, 200) transaction.commit() self.assertIn('doc1', self.folder1.objectIds()) self.assertIn('doc2', self.folder1.objectIds()) self.assertNotIn('doc1', self.portal.objectIds()) self.assertNotIn('doc2', self.portal.objectIds()) def test_copy_without_source_raises_400(self): response = self.api_session.post('/folder1/@copy') self.assertEquals(response.status_code, 400) def test_copy_not_existing_object(self): response = self.api_session.post('/@copy', json={"source": "does-not-exist"}) self.assertEquals(response.status_code, 200) self.assertEqual([], response.json()) def test_copy_multiple_objects(self): response = self.api_session.post( '/@copy', json={ "source": [self.doc1.absolute_url(), self.doc2.absolute_url()] }) transaction.commit() self.assertEquals(response.status_code, 200) self.assertIn('copy_of_doc1', self.portal.objectIds()) self.assertIn('copy_of_doc2', self.portal.objectIds()) def test_copy_single_object_no_permissions_raises_403(self): self.api_session.auth = ('memberuser', 'secret') response = self.api_session.post( '/@copy', json={"source": self.doc1.absolute_url()}) self.assertEquals(response.status_code, 403) def test_copy_single_object_no_auth_raises_401(self): self.api_session.auth = ('nonexistent', 'secret') response = self.api_session.post( '/@copy', json={"source": self.doc1.absolute_url()}) self.assertEquals(response.status_code, 401)
class TestSearchFunctional(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() self.request = self.portal.REQUEST self.catalog = getToolByName(self.portal, "portal_catalog") self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) # /plone/folder self.folder = createContentInContainer(self.portal, u"Folder", id=u"folder", title=u"Some Folder") # /plone/folder/doc self.doc = createContentInContainer( self.folder, u"DXTestDocument", id="doc", title=u"Lorem Ipsum", start=DateTime(1950, 1, 1, 0, 0), effective=DateTime(1995, 1, 1, 0, 0), expires=DateTime(1999, 1, 1, 0, 0), test_int_field=42, test_list_field=["Keyword1", "Keyword2", "Keyword3"], test_bool_field=True, test_richtext_field=RichTextValue( raw=u"<p>Some Text</p>", mimeType="text/html", outputMimeType="text/html", ), ) IMutableUUID(self.doc).set("77779ffa110e45afb1ba502f75f77777") self.doc.reindexObject() # /plone/folder/other-document self.doc2 = createContentInContainer( self.folder, u"DXTestDocument", id="other-document", title=u"Other Document", description=u"\xdcbersicht", start=DateTime(1975, 1, 1, 0, 0), effective=DateTime(2015, 1, 1, 0, 0), expires=DateTime(2020, 1, 1, 0, 0), test_list_field=["Keyword2", "Keyword3"], test_bool_field=False, ) # /plone/folder2 self.folder2 = createContentInContainer(self.portal, u"Folder", id=u"folder2", title=u"Another Folder") # /plone/folder2/doc createContentInContainer( self.folder2, u"DXTestDocument", id="doc", title=u"Document in second folder", start=DateTime(1975, 1, 1, 0, 0), effective=DateTime(2015, 1, 1, 0, 0), expires=DateTime(2020, 1, 1, 0, 0), test_bool_field=False, ) # /plone/doc-outside-folder createContentInContainer( self.portal, u"DXTestDocument", id="doc-outside-folder", title=u"Doc outside folder", ) transaction.commit() def tearDown(self): self.api_session.close() def test_overall_response_format(self): response = self.api_session.get("/@search") self.assertEqual(response.status_code, 200) self.assertEqual(response.headers.get("Content-Type"), "application/json") results = response.json() self.assertEqual( results[u"items_total"], len(results[u"items"]), "items_total property should match actual item count.", ) def test_search_on_context_constrains_query_by_path(self): response = self.api_session.get("/folder/@search") self.assertSetEqual( { u"/plone/folder", u"/plone/folder/doc", u"/plone/folder/other-document" }, set(result_paths(response.json())), ) def test_search_in_vhm(self): # Install a Virtual Host Monster if "virtual_hosting" not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster import ( manage_addVirtualHostMonster, ) manage_addVirtualHostMonster(self.app, "virtual_hosting") transaction.commit() # we don't get a result if we do not provide the full physical path response = self.api_session.get("/@search?path=/folder") self.assertSetEqual(set(), set(result_paths(response.json()))) # If we go through the VHM will will get results if we only use # the part of the path inside the VHM vhm_url = "%s/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/%s" % ( self.app.absolute_url(), "@search?path=/folder", ) response = self.api_session.get(vhm_url) self.assertSetEqual( {u"/folder", u"/folder/doc", u"/folder/other-document"}, set(result_paths(response.json())), ) def test_search_in_vhm_multiple_paths(self): # Install a Virtual Host Monster if "virtual_hosting" not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster import ( manage_addVirtualHostMonster, ) manage_addVirtualHostMonster(self.app, "virtual_hosting") transaction.commit() # path as a list query = {"path": ["/folder", "/folder2"]} # If we go through the VHM we will get results for multiple paths # if we only use the part of the path inside the VHM vhm_url = "%s/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/%s" % ( self.app.absolute_url(), "@search", ) response = self.api_session.get(vhm_url, params=query) self.assertSetEqual( { u"/folder", u"/folder/doc", u"/folder/other-document", u"/folder2", u"/folder2/doc", }, set(result_paths(response.json())), ) # path as a dict with a query list query = {"path.query": ["/folder", "/folder2"]} # If we go through the VHM we will get results for multiple paths # if we only use the part of the path inside the VHM vhm_url = "%s/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/%s" % ( self.app.absolute_url(), "@search", ) response = self.api_session.get(vhm_url, params=query) self.assertSetEqual( { u"/folder", u"/folder/doc", u"/folder/other-document", u"/folder2", u"/folder2/doc", }, set(result_paths(response.json())), ) def test_path_gets_prefilled_if_missing_from_path_query_dict(self): response = self.api_session.get("/@search?path.depth=1") self.assertSetEqual( { u"/plone/folder", u"/plone/folder2", u"/plone/doc-outside-folder" }, set(result_paths(response.json())), ) def test_partial_metadata_retrieval(self): query = { "SearchableText": "lorem", "metadata_fields": ["portal_type", "review_state"], } response = self.api_session.get("/@search", params=query) self.assertDictContainsSubset( { u"@id": self.portal_url + u"/folder/doc", u"title": u"Lorem Ipsum", u"portal_type": u"DXTestDocument", u"review_state": u"private", }, response.json()["items"][0], ) def test_full_metadata_retrieval(self): query = {"SearchableText": "lorem", "metadata_fields": "_all"} response = self.api_session.get("/@search", params=query) first_item = response.json()["items"][0] self.assertDictContainsSubset( { u"@id": self.portal_url + u"/folder/doc", u"Creator": u"test_user_1_", u"Description": u"", u"EffectiveDate": u"None", u"ExpirationDate": u"None", u"Subject": [], u"Title": u"Lorem Ipsum", u"Type": u"DX Test Document", u"UID": u"77779ffa110e45afb1ba502f75f77777", u"author_name": None, u"cmf_uid": None, u"commentators": [], u"description": u"", u"effective": u"1995-01-01T00:00:00+00:00", u"end": None, u"exclude_from_nav": False, u"expires": u"1999-01-01T00:00:00+00:00", u"getId": u"doc", u"getPath": u"/plone/folder/doc", u"getRemoteUrl": None, u"getURL": self.portal_url + u"/folder/doc", u"id": u"doc", u"in_response_to": None, u"is_folderish": False, u"last_comment_date": None, u"listCreators": [u"test_user_1_"], u"location": None, u"meta_type": u"Dexterity Item", u"portal_type": u"DXTestDocument", u"review_state": u"private", u"start": u"1950-01-01T00:00:00+00:00", u"sync_uid": None, u"title": u"Lorem Ipsum", u"total_comments": 0, }, first_item, ) # This value changed in Plone 5.2 # (Dexterity gained support for getObjSize) self.assertIn(first_item[u"getObjSize"], (u"0 KB", u"1 KB")) def test_full_objects_retrieval(self): query = { "SearchableText": "lorem", "metadata_fields": ["portal_type", "review_state"], "fullobjects": True, } response = self.api_session.get("/@search", params=query) self.assertEqual( u"<p>Some Text</p>", response.json()["items"][0]["test_richtext_field"]["data"], ) self.assertEqual(self.portal_url + u"/folder/doc", response.json()["items"][0]["@id"]) def test_full_objects_retrieval_discussion(self): # Allow discussion registry = getUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.globally_enabled = True self.doc.allow_discussion = True transaction.commit() url = "{}/@comments".format(self.doc.absolute_url()) self.api_session.post(url, json={"text": "comment 1"}) transaction.commit() query = {"portal_type": "Discussion Item", "fullobjects": True} response = self.api_session.get("/@search", params=query) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()["items"]), 1) def test_full_objects_retrieval_collections(self): self.collection = createContentInContainer(self.folder, u"Collection", id="collection") transaction.commit() query = {"portal_type": "Collection", "fullobjects": True} response = self.api_session.get("/@search", params=query) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()["items"]), 1) # ZCTextIndex def test_fulltext_search(self): query = {"SearchableText": "lorem"} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) def test_fulltext_search_with_non_ascii_characters(self): query = {"SearchableText": u"\xfcbersicht"} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/other-document"], result_paths(response.json())) # KeywordIndex def test_keyword_index_str_query(self): query = {"test_list_field": "Keyword1"} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) def test_keyword_index_str_query_or(self): query = {"test_list_field": ["Keyword2", "Keyword3"]} response = self.api_session.get("/@search", params=query) self.assertEqual( [u"/plone/folder/doc", u"/plone/folder/other-document"], result_paths(response.json()), ) def test_keyword_index_str_query_and(self): query = { "test_list_field.query": ["Keyword1", "Keyword2"], "test_list_field.operator": "and", } response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) @unittest.skipIf(six.PY3, "Python 3 can't sort mixed types") def test_keyword_index_int_query(self): self.doc.test_list_field = [42, 23] self.doc.reindexObject() transaction.commit() query = {"test_list_field:int": 42} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) # BooleanIndex def test_boolean_index_query(self): query = {"test_bool_field": True, "portal_type": "DXTestDocument"} response = self.api_session.get("/folder/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) query = {"test_bool_field": False, "portal_type": "DXTestDocument"} response = self.api_session.get("/folder/@search", params=query) self.assertEqual([u"/plone/folder/other-document"], result_paths(response.json())) # FieldIndex def test_field_index_int_query(self): query = {"test_int_field:int": 42} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) def test_field_index_int_range_query(self): query = { "test_int_field.query:int": [41, 43], "test_int_field.range": "min:max", } response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) # ExtendedPathIndex def test_extended_path_index_query(self): query = {"path": "/".join(self.folder.getPhysicalPath())} response = self.api_session.get("/@search", params=query) self.assertEqual( sorted([ u"/plone/folder", u"/plone/folder/doc", u"/plone/folder/other-document", ]), sorted(result_paths(response.json())), ) def test_extended_path_index_query_multiple(self): # path as a list query = { "path": [ "/".join(self.folder.getPhysicalPath()), "/".join(self.folder2.getPhysicalPath()), ] } response = self.api_session.get("/@search", params=query) self.assertEqual( sorted([ u"/plone/folder", u"/plone/folder/doc", u"/plone/folder/other-document", u"/plone/folder2", u"/plone/folder2/doc", ]), sorted(result_paths(response.json())), ) # path as a dict with a query list query = { "path.query": [ "/".join(self.folder.getPhysicalPath()), "/".join(self.folder2.getPhysicalPath()), ] } response = self.api_session.get("/@search", params=query) self.assertEqual( sorted([ u"/plone/folder", u"/plone/folder/doc", u"/plone/folder/other-document", u"/plone/folder2", u"/plone/folder2/doc", ]), sorted(result_paths(response.json())), ) def test_extended_path_index_depth_limiting(self): lvl1 = createContentInContainer(self.portal, u"Folder", id=u"lvl1") lvl2 = createContentInContainer(lvl1, u"Folder", id=u"lvl2") createContentInContainer(lvl2, u"Folder", id=u"lvl3") transaction.commit() path = "/plone/lvl1" # Depth 0 - only object identified by path query = {"path.query": path, "path.depth": 0} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/lvl1"], result_paths(response.json())) # Depth 1 - immediate children query = {"path.query": path, "path.depth": 1} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/lvl1/lvl2"], result_paths(response.json())) # No depth - object itself and all children query = {"path": path} response = self.api_session.get("/@search", params=query) self.assertSetEqual( {u"/plone/lvl1", u"/plone/lvl1/lvl2", u"/plone/lvl1/lvl2/lvl3"}, set(result_paths(response.json())), ) # DateIndex def test_date_index_query(self): query = {"start": date(1950, 1, 1).isoformat()} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) def test_date_index_ranged_query(self): query = { "start.query": [date(1949, 1, 1).isoformat(), date(1951, 1, 1).isoformat()], "start.range": "min:max", } response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json())) # DateRangeIndex def test_date_range_index_query(self): query = {"effectiveRange": date(1997, 1, 1).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual(2, len(result_paths(response.json()))) self.assertTrue(u"/plone/folder" in result_paths(response.json())) self.assertTrue(u"/plone/folder/doc" in result_paths(response.json())) # DateRecurringIndex def test_date_recurring_index_query(self): from datetime import datetime createContentInContainer( self.folder, u"Event", id=u"event", title=u"Event", start=datetime(2013, 1, 1, 0, 0), end=datetime(2013, 1, 1, 23, 59), whole_day=True, recurrence="FREQ=DAILY;COUNT=10;INTERVAL=2", timezone="UTC", ) import transaction transaction.commit() # First occurrence query = {"start": date(2013, 1, 1).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual([u"/plone/folder/event"], result_paths(response.json())) # No event that day query = {"start": date(2013, 1, 2).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual([], result_paths(response.json())) # Second occurrence query = {"start": date(2013, 1, 3).isoformat()} response = self.api_session.get("/folder/@search", params=query) self.assertEqual([u"/plone/folder/event"], result_paths(response.json())) # Ranged query query = { "start.query": [date(2013, 1, 1).isoformat(), date(2013, 1, 5).isoformat()], "start.range": "min:max", } response = self.api_session.get("/folder/@search", params=query) self.assertEqual([u"/plone/folder/event"], result_paths(response.json())) # UUIDIndex def test_uuid_index_query(self): IMutableUUID(self.doc).set("7777a074cb4240d08c9a129e3a837777") self.doc.reindexObject() transaction.commit() query = {"UID": "7777a074cb4240d08c9a129e3a837777"} response = self.api_session.get("/@search", params=query) self.assertEqual([u"/plone/folder/doc"], result_paths(response.json()))
class TestSearchFunctional(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() self.request = self.portal.REQUEST self.catalog = getToolByName(self.portal, 'portal_catalog') self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) # /plone/folder self.folder = createContentInContainer( self.portal, u'Folder', id=u'folder', title=u'Some Folder') # /plone/folder/doc self.doc = createContentInContainer( self.folder, u'DXTestDocument', id='doc', title=u'Lorem Ipsum', start=DateTime(1950, 1, 1, 0, 0), effective=DateTime(1995, 1, 1, 0, 0), expires=DateTime(1999, 1, 1, 0, 0), test_int_field=42, test_list_field=['Keyword1', 'Keyword2', 'Keyword3'], test_bool_field=True, test_richtext_field=RichTextValue( raw=u'<p>Some Text</p>', mimeType='text/html', outputMimeType='text/html' ), ) IMutableUUID(self.doc).set('77779ffa110e45afb1ba502f75f77777') self.doc.reindexObject() # /plone/folder/other-document self.doc2 = createContentInContainer( self.folder, u'DXTestDocument', id='other-document', title=u'Other Document', description=u'\xdcbersicht', start=DateTime(1975, 1, 1, 0, 0), effective=DateTime(2015, 1, 1, 0, 0), expires=DateTime(2020, 1, 1, 0, 0), test_list_field=['Keyword2', 'Keyword3'], test_bool_field=False, ) # /plone/doc-outside-folder createContentInContainer( self.portal, u'DXTestDocument', id='doc-outside-folder', title=u'Doc outside folder', ) transaction.commit() def test_overall_response_format(self): response = self.api_session.get('/@search') self.assertEqual(response.status_code, 200) self.assertEqual( response.headers.get('Content-Type'), 'application/json', ) results = response.json() self.assertEqual( results[u'items_total'], len(results[u'items']), 'items_total property should match actual item count.' ) def test_search_on_context_constrains_query_by_path(self): response = self.api_session.get('/folder/@search') self.assertSetEqual( {u'/plone/folder', u'/plone/folder/doc', u'/plone/folder/other-document'}, set(result_paths(response.json()))) def test_search_in_vhm(self): # Install a Virtual Host Monster if 'virtual_hosting' not in self.app.objectIds(): # If ZopeLite was imported, we have no default virtual # host monster from Products.SiteAccess.VirtualHostMonster \ import manage_addVirtualHostMonster manage_addVirtualHostMonster(self.app, 'virtual_hosting') transaction.commit() # we don't get a result if we do not provide the full physical path response = self.api_session.get('/@search?path=/folder',) self.assertSetEqual(set(), set(result_paths(response.json()))) # If we go through the VHM will will get results if we only use # the part of the path inside the VHM vhm_url = ( '%s/VirtualHostBase/http/plone.org/plone/VirtualHostRoot/%s' % (self.app.absolute_url(), '@search?path=/folder')) response = self.api_session.get(vhm_url) self.assertSetEqual( {u'/folder', u'/folder/doc', u'/folder/other-document'}, set(result_paths(response.json()))) def test_path_gets_prefilled_if_missing_from_path_query_dict(self): response = self.api_session.get('/@search?path.depth=1') self.assertSetEqual( {u'/plone/folder', u'/plone/doc-outside-folder'}, set(result_paths(response.json()))) def test_partial_metadata_retrieval(self): query = {'SearchableText': 'lorem', 'metadata_fields': ['portal_type', 'review_state']} response = self.api_session.get('/@search', params=query) self.assertDictContainsSubset( {u'@id': u'http://*****:*****@search', params=query) self.assertDictContainsSubset( {u'@id': u'http://*****:*****@search', params=query) self.assertEqual( u'<p>Some Text</p>', response.json()['items'][0]['test_richtext_field']['data']) self.assertEqual( 'http://*****:*****@id']) def test_full_objects_retrieval_discussion(self): # Allow discussion registry = getUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.globally_enabled = True self.doc.allow_discussion = True transaction.commit() url = '{}/@comments'.format(self.doc.absolute_url()) self.api_session.post(url, json={'text': 'comment 1'}) transaction.commit() query = {'portal_type': 'Discussion Item', 'fullobjects': True} response = self.api_session.get('/@search', params=query) self.assertEquals(response.status_code, 200) self.assertEquals(len(response.json()['items']), 1) def test_full_objects_retrieval_collections(self): self.collection = createContentInContainer( self.folder, u'Collection', id='collection', ) transaction.commit() query = {'portal_type': 'Collection', 'fullobjects': True} response = self.api_session.get('/@search', params=query) self.assertEquals(response.status_code, 200) self.assertEquals(len(response.json()['items']), 1) # ZCTextIndex def test_fulltext_search(self): query = {'SearchableText': 'lorem'} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) def test_fulltext_search_with_non_ascii_characters(self): query = {'SearchableText': u'\xfcbersicht'} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/other-document'], result_paths(response.json()) ) # KeywordIndex def test_keyword_index_str_query(self): query = {'test_list_field': 'Keyword1'} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) def test_keyword_index_str_query_or(self): query = {'test_list_field': ['Keyword2', 'Keyword3']} response = self.api_session.get('/@search', params=query) self.assertItemsEqual( [u'/plone/folder/doc', u'/plone/folder/other-document'], result_paths(response.json()) ) def test_keyword_index_str_query_and(self): query = { 'test_list_field.query': ['Keyword1', 'Keyword2'], 'test_list_field.operator': 'and', } response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) def test_keyword_index_int_query(self): self.doc.test_list_field = [42, 23] self.doc.reindexObject() transaction.commit() query = {'test_list_field:int': 42} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) # BooleanIndex def test_boolean_index_query(self): query = {'test_bool_field': True, 'portal_type': 'DXTestDocument'} response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) query = {'test_bool_field': False, 'portal_type': 'DXTestDocument'} response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [u'/plone/folder/other-document'], result_paths(response.json()) ) # FieldIndex def test_field_index_int_query(self): query = {'test_int_field:int': 42} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) def test_field_index_int_range_query(self): query = { 'test_int_field.query:int': [41, 43], 'test_int_field.range': 'min:max', } response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) # ExtendedPathIndex def test_extended_path_index_query(self): query = {'path': '/'.join(self.folder.getPhysicalPath())} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder', u'/plone/folder/doc', u'/plone/folder/other-document'], result_paths(response.json()) ) def test_extended_path_index_depth_limiting(self): lvl1 = createContentInContainer(self.portal, u'Folder', id=u'lvl1') lvl2 = createContentInContainer(lvl1, u'Folder', id=u'lvl2') createContentInContainer(lvl2, u'Folder', id=u'lvl3') transaction.commit() path = '/plone/lvl1' # Depth 0 - only object identified by path query = {'path.query': path, 'path.depth': 0} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/lvl1'], result_paths(response.json())) # Depth 1 - immediate children query = {'path.query': path, 'path.depth': 1} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/lvl1/lvl2'], result_paths(response.json())) # No depth - object itself and all children query = {'path': path} response = self.api_session.get('/@search', params=query) self.assertSetEqual( {u'/plone/lvl1', u'/plone/lvl1/lvl2', u'/plone/lvl1/lvl2/lvl3'}, set(result_paths(response.json()))) # DateIndex def test_date_index_query(self): query = {'start': date(1950, 1, 1).isoformat()} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) def test_date_index_ranged_query(self): query = { 'start.query': [ date(1949, 1, 1).isoformat(), date(1951, 1, 1).isoformat(), ], 'start.range': 'min:max', } response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) ) # DateRangeIndex def test_date_range_index_query(self): query = {'effectiveRange': date(1997, 1, 1).isoformat()} response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [u'/plone/folder', u'/plone/folder/doc'], result_paths(response.json()) ) # DateRecurringIndex def test_date_recurring_index_query(self): from datetime import datetime createContentInContainer( self.folder, u'Event', id=u'event', title=u'Event', start=datetime(2013, 1, 1, 0, 0), end=datetime(2013, 1, 1, 23, 59), whole_day=True, recurrence='FREQ=DAILY;COUNT=10;INTERVAL=2', timezone='UTC', ) import transaction transaction.commit() # First occurrence query = {'start': date(2013, 1, 1).isoformat()} response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [u'/plone/folder/event'], result_paths(response.json()) ) # No event that day query = {'start': date(2013, 1, 2).isoformat()} response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [], result_paths(response.json()) ) # Second occurrence query = {'start': date(2013, 1, 3).isoformat()} response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [u'/plone/folder/event'], result_paths(response.json()) ) # Ranged query query = { 'start.query': [date(2013, 1, 1).isoformat(), date(2013, 1, 5).isoformat()], 'start.range': 'min:max', } response = self.api_session.get('/folder/@search', params=query) self.assertEqual( [u'/plone/folder/event'], result_paths(response.json()) ) # UUIDIndex def test_uuid_index_query(self): IMutableUUID(self.doc).set('7777a074cb4240d08c9a129e3a837777') self.doc.reindexObject() transaction.commit() query = {'UID': '7777a074cb4240d08c9a129e3a837777'} response = self.api_session.get('/@search', params=query) self.assertEqual( [u'/plone/folder/doc'], result_paths(response.json()) )
class TestGroupsEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.request = self.layer['request'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Manager']) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.gtool = api.portal.get_tool('portal_groups') properties = { 'title': 'Plone Team', 'description': 'We are Plone', 'email': '*****@*****.**', } self.gtool.addGroup('ploneteam', (), (), properties=properties, title=properties['title'], description=properties['description']) transaction.commit() def tearDown(self): self.api_session.close() def test_list_groups(self): response = self.api_session.get('/@groups') self.assertEqual(200, response.status_code) self.assertEqual(5, len(response.json())) user_ids = [group['id'] for group in response.json()] self.assertIn('Administrators', user_ids) self.assertIn('Reviewers', user_ids) self.assertIn('AuthenticatedUsers', user_ids) self.assertIn('ploneteam', user_ids) ptgroup = [ x for x in response.json() if x.get('groupname') == 'ploneteam' ][0] self.assertEqual('ploneteam', ptgroup.get('id')) self.assertEqual(self.portal.absolute_url() + '/@groups/ploneteam', ptgroup.get('@id')) self.assertEqual('*****@*****.**', ptgroup.get('email')) self.assertEqual('Plone Team', ptgroup.get('title')) self.assertEqual('We are Plone', ptgroup.get('description')) # We don't want the group members listed in the overview as there # might be loads. self.assertTrue( not any(['users' in group for group in response.json()]), "Users key found in groups listing") def test_add_group(self): response = self.api_session.post( '/@groups', json={ 'groupname': 'fwt', 'email': '*****@*****.**', 'title': 'Framework Team', 'description': 'The Plone Framework Team', 'roles': ['Manager'], 'groups': ['Administrators'], 'users': [SITE_OWNER_NAME, TEST_USER_ID] }, ) transaction.commit() self.assertEqual(201, response.status_code) fwt = self.gtool.getGroupById('fwt') self.assertEqual("*****@*****.**", fwt.getProperty('email')) self.assertTrue( set([SITE_OWNER_NAME, TEST_USER_ID]).issubset(set(fwt.getGroupMemberIds())), "Userids not found in group") def test_add_group_groupname_is_required(self): response = self.api_session.post( '/@groups', json={"title": "Framework Team"}, ) transaction.commit() self.assertEqual(400, response.status_code) self.assertTrue('"Property \'groupname\' is required' in response.text) def test_get_group(self): response = self.api_session.get('/@groups/ploneteam') self.assertEqual(response.status_code, 200) self.assertEqual('ploneteam', response.json().get('id')) self.assertEqual(self.portal.absolute_url() + '/@groups/ploneteam', response.json().get('@id')) self.assertEqual('*****@*****.**', response.json().get('email')) self.assertEqual('*****@*****.**', response.json().get('email')) self.assertEqual('Plone Team', response.json().get('title')) self.assertEqual('We are Plone', response.json().get('description')) self.assertIn('users', response.json()) def test_get_search_group_with_filter(self): response = self.api_session.get('/@groups', params={'query': 'plo'}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual('ploneteam', response.json()[0].get('id')) self.assertEqual(self.portal.absolute_url() + '/@groups/ploneteam', response.json()[0].get('@id')) self.assertEqual('*****@*****.**', response.json()[0].get('email')) response = self.api_session.get('/@groups', params={'query': 'Auth'}) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 1) self.assertEqual('AuthenticatedUsers', response.json()[0].get('id')) def test_get_non_existing_group(self): response = self.api_session.get('/@groups/non-existing-group') self.assertEqual(response.status_code, 404) def test_update_group(self): ploneteam = self.gtool.getGroupById('ploneteam') ploneteam.addMember(SITE_OWNER_NAME) transaction.commit() self.assertNotIn(TEST_USER_ID, ploneteam.getGroupMemberIds()) self.assertIn(SITE_OWNER_NAME, ploneteam.getGroupMemberIds()) payload = { 'groupname': 'ploneteam', 'email': '*****@*****.**', 'users': { TEST_USER_ID: True, SITE_OWNER_NAME: False, } } response = self.api_session.patch('/@groups/ploneteam', json=payload) transaction.commit() self.assertEqual(response.status_code, 204) ploneteam = self.gtool.getGroupById('ploneteam') self.assertEqual('ploneteam', ploneteam.id) self.assertEqual('Plone Team', ploneteam.getProperty('title')) self.assertEqual('*****@*****.**', ploneteam.getProperty('email')) self.assertIn(TEST_USER_ID, ploneteam.getGroupMemberIds()) self.assertNotIn(SITE_OWNER_NAME, ploneteam.getGroupMemberIds()) def test_delete_group(self): response = self.api_session.delete('/@groups/ploneteam') transaction.commit() self.assertEqual(response.status_code, 204) self.assertEqual(None, self.gtool.getGroupById('ploneteam')) def test_delete_non_existing_group(self): response = self.api_session.delete('/@groups/non-existing-group') transaction.commit() self.assertEqual(response.status_code, 404)
class EmailSendEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.mailhost = getUtility(IMailHost) registry = getUtility(IRegistry) registry["plone.email_from_address"] = "*****@*****.**" registry["plone.email_from_name"] = "Plone test site" self.api_session = RelativeSession(self.portal_url, test=self) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.anon_api_session = RelativeSession(self.portal_url, test=self) self.anon_api_session.headers.update({"Accept": "application/json"}) transaction.commit() def tearDown(self): self.api_session.close() self.anon_api_session.close() def test_email_send(self): response = self.api_session.post( "/@email-send", json={ "to": "*****@*****.**", "from": "*****@*****.**", "message": "Just want to say hi.", }, ) transaction.commit() self.assertEqual(response.status_code, 204) msg = self.mailhost.messages[0] if isinstance(msg, bytes) and bytes is not str: # Python 3 with Products.MailHost 4.10+ msg = msg.decode("utf-8") self.assertTrue( "Subject: =?utf-8?q?A_portal_user_via_Plone_site?=" in msg) self.assertTrue("From: [email protected]" in msg) self.assertTrue("Reply-To: [email protected]" in msg) self.assertTrue("Just want to say hi." in msg) def test_email_send_all_parameters(self): response = self.api_session.post( "/@email-send", json={ "to": "*****@*****.**", "from": "*****@*****.**", "message": "Just want to say hi.", "name": "John Doe", "subject": "This is the subject.", }, ) transaction.commit() self.assertEqual(response.status_code, 204) msg = self.mailhost.messages[0] if isinstance(msg, bytes) and bytes is not str: # Python 3 with Products.MailHost 4.10+ msg = msg.decode("utf-8") self.assertTrue("=?utf-8?q?This_is_the_subject" in msg) self.assertTrue("From: [email protected]" in msg) self.assertTrue("John Doe" in msg) self.assertTrue("Reply-To: [email protected]" in msg) self.assertTrue("Just want to say hi." in msg) def test_email_send_anonymous(self): response = self.anon_api_session.post( "/@email-send", json={ "to": "*****@*****.**", "from": "*****@*****.**", "message": "Just want to say hi.", "name": "John Doe", "subject": "This is the subject.", }, ) self.assertEqual(response.status_code, 401)
class TestQuerystringSearchEndpoint(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.portal.invokeFactory("Document", "testdocument", title="Test Document") self.doc = self.portal.testdocument transaction.commit() def tearDown(self): self.api_session.close() def test_querystringsearch_basic(self): response = self.api_session.post( "/@querystring-search", json={ "query": [ { "i": "portal_type", "o": "plone.app.querystring.operation.selection.is", "v": ["Document"], } ] }, ) self.assertEqual(response.status_code, 200) self.assertIn("items", response.json()) self.assertIn("items_total", response.json()) self.assertEqual(response.json()["items_total"], 1) self.assertEqual(len(response.json()["items"]), 1) self.assertNotIn("effective", response.json()["items"][0]) def test_querystringsearch_fullobjects(self): response = self.api_session.post( "/@querystring-search", json={ "query": [ { "i": "portal_type", "o": "plone.app.querystring.operation.selection.is", "v": ["Document"], } ], "fullobjects": True, }, ) self.assertEqual(response.status_code, 200) self.assertIn("items", response.json()) self.assertIn("items_total", response.json()) self.assertIn("effective", response.json()["items"][0]) self.assertEqual(response.json()["items_total"], 1) self.assertEqual(len(response.json()["items"]), 1) def test_querystringsearch_complex(self): for a in range(1, 10): self.portal.invokeFactory( "Document", "testdocument" + str(a), title="Test Document " + str(a) ) self.doc = self.portal.testdocument transaction.commit() response = self.api_session.post( "/@querystring-search", json={ "query": [ { "i": "portal_type", "o": "plone.app.querystring.operation.selection.is", "v": ["Document"], } ], "b_size": 5, }, ) self.assertEqual(response.status_code, 200) self.assertIn("items", response.json()) self.assertIn("items_total", response.json()) self.assertEqual(response.json()["items_total"], 10) self.assertEqual(len(response.json()["items"]), 5) self.assertNotIn("effective", response.json()["items"][0]) self.assertEqual(response.json()["items"][4]["title"], u"Test Document 4") response = self.api_session.post( "/@querystring-search", json={ "query": [ { "i": "portal_type", "o": "plone.app.querystring.operation.selection.is", "v": ["Document"], } ], "b_size": 5, "b_start": 5, }, ) self.assertEqual(response.status_code, 200) self.assertIn("items", response.json()) self.assertIn("items_total", response.json()) self.assertEqual(response.json()["items_total"], 10) self.assertEqual(len(response.json()["items"]), 5) self.assertNotIn("effective", response.json()["items"][0]) self.assertEqual(response.json()["items"][4]["title"], u"Test Document 9") @unittest.skipIf( not SUPPORT_NOT_UUID_QUERIES, "Skipping because ZCatalog allows not queries on UUIDIndex from >=5.1", ) def test_querystringsearch_do_not_return_context(self): self.portal.invokeFactory("Document", "testdocument2", title="Test Document 2") self.doc = self.portal.testdocument transaction.commit() response = self.api_session.post( "/testdocument/@querystring-search", json={ "query": [ { "i": "portal_type", "o": "plone.app.querystring.operation.selection.is", "v": ["Document"], } ], }, ) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["items_total"], 1) self.assertEqual( response.json()["items"][0]["@id"], "http://localhost:55001/plone/testdocument2", )
class TestDexterityTypesControlpanel(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.request = self.layer["request"] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({"Accept": "application/json"}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) def tearDown(self): self.api_session.close() def test_controlpanels_dexterity_types_get(self): response = self.api_session.get("/@controlpanels/dexterity-types") self.assertEqual(200, response.status_code) self.assertEqual( [ "Collection", "Document", "Folder", "Link", "File", "Image", "News Item", "Event", "DXTestDocument", ], [ x.get("id") for x in self.api_session.get( "/@controlpanels/dexterity-types").json().get("items") ], ) def test_controlpanels_dexterity_types_document_get(self): response = self.api_session.get( "/@controlpanels/dexterity-types/Document") self.assertEqual(200, response.status_code) self.assertEqual( "{}/@controlpanels/dexterity-types/Document".format( self.portal_url), response.json().get("@id"), ) self.assertEqual("Page", response.json().get("title")) def test_controlpanels_dexterity_types_post(self): response = self.api_session.post( "/@controlpanels/dexterity-types", json={ "title": "My Custom Content Type", "description": "A custom content-type", }, ) self.assertEqual(201, response.status_code) self.assertEqual( "{}/@controlpanels/dexterity-types/my_custom_content_type".format( self.portal_url), response.json().get("@id"), ) self.assertEqual("My Custom Content Type", response.json().get("title")) self.assertEqual("A custom content-type", response.json().get("description")) def test_controlpanels_dexterity_types_document_patch(self): response = self.api_session.patch( "/@controlpanels/dexterity-types/Document", json={ "title": "New Content Type Title", "description": "New content type description", }, ) # PATCH returns no content self.assertEqual(204, response.status_code) response = self.api_session.get( "/@controlpanels/dexterity-types/Document") self.assertEqual(200, response.status_code) self.assertEqual("New Content Type Title", response.json().get("title")) self.assertEqual("New content type description", response.json().get("description")) def test_controlpanels_dexterity_types_document_delete(self): response = self.api_session.delete( "/@controlpanels/dexterity-types/Document") self.assertEqual(204, response.status_code) self.assertEqual( [ "Collection", "Folder", "Link", "File", "Image", "News Item", "Event", "DXTestDocument", ], [ x.get("id") for x in self.api_session.get( "/@controlpanels/dexterity-types").json().get("items") ], )
class TestContentPatch(unittest.TestCase): layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): self.app = self.layer['app'] self.portal = self.layer['portal'] self.portal_url = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ['Member']) login(self.portal, SITE_OWNER_NAME) self.api_session = RelativeSession(self.portal_url) self.api_session.headers.update({'Accept': 'application/json'}) self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) self.portal.invokeFactory('Document', id='doc1', title='My Document') wftool = getToolByName(self.portal, 'portal_workflow') wftool.doActionFor(self.portal.doc1, 'publish') transaction.commit() def test_patch_document(self): response = requests.patch( self.portal.doc1.absolute_url(), headers={'Accept': 'application/json'}, auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), data='{"title": "Patched Document"}', ) self.assertEqual(204, response.status_code) transaction.begin() self.assertEqual("Patched Document", self.portal.doc1.Title()) def test_patch_document_with_invalid_body_returns_400(self): response = requests.patch( self.portal.doc1.absolute_url(), headers={'Accept': 'application/json'}, auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), data='foo', ) self.assertEqual(400, response.status_code) self.assertIn('DeserializationError', response.text) def test_patch_undeserializable_object_returns_501(self): obj = PortalContent() obj.id = 'obj1' obj.portal_type = 'Undeserializable Type' self.portal._setObject(obj.id, obj) transaction.commit() response = requests.patch( self.portal.obj1.absolute_url(), headers={'Accept': 'application/json'}, auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), data='{"id": "patched_obj1"}', ) self.assertEqual(501, response.status_code) self.assertIn('Undeserializable Type', response.text) def test_patch_document_returns_401_unauthorized(self): response = requests.patch( self.portal.doc1.absolute_url(), headers={'Accept': 'application/json'}, auth=(TEST_USER_NAME, TEST_USER_PASSWORD), data='{"title": "Patched Document"}', ) self.assertEqual(401, response.status_code) def test_patch_feed_event_with_get_contents(self): start_date = DateTime(datetime.datetime.today() + datetime.timedelta(days=1)).ISO8601() end_date = DateTime(datetime.datetime.today() + datetime.timedelta(days=1, hours=1)).ISO8601() response = self.api_session.post( '/', json={ "title": "An Event", "@type": "Event", "start": start_date, "end": end_date, "timezone": "Europe/Vienna" }, ) self.assertEqual(201, response.status_code) response = response.json() event_id = response['id'] two_days_ahead = DateTime(datetime.datetime.today() + datetime.timedelta(days=2)) response = self.api_session.patch('/{}'.format(event_id), json={ "start": response['start'], "end": two_days_ahead.ISO8601() }) self.assertEqual(204, response.status_code) response = self.api_session.get('/{}'.format(event_id)) response = response.json() self.assertEquals( DateTime(response['end']).day(), two_days_ahead.day()) self.assertEquals( DateTime(response['end']).hour(), two_days_ahead.hour())