This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
plone.api/src/plone/api/tests/test_content.py

679 lines
24 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for plone.api.content."""
from Acquisition import aq_base
from OFS.CopySupport import CopyError
from Products.CMFCore.interfaces import IContentish
from Products.ZCatalog.interfaces import IZCatalog
from plone import api
from plone.api.tests.base import INTEGRATION_TESTING
from plone.indexer import indexer
from plone.uuid.interfaces import IMutableUUID
from plone.uuid.interfaces import IUUIDGenerator
from zExceptions import BadRequest
from zope.component import getUtility
from zope.component import getGlobalSiteManager
import mock
import pkg_resources
import unittest2 as unittest
try:
pkg_resources.get_distribution('plone.dexterity')
except pkg_resources.DistributionNotFound:
HAS_DEXTERITY = False
else:
HAS_DEXTERITY = True
class TestPloneApiContent(unittest.TestCase):
"""Unit tests for content manipulation using plone.api"""
layer = INTEGRATION_TESTING
def setUp(self):
"""Create a portal structure which we can test against.
Plone (portal root)
|-- blog
|-- about
| |-- team
| `-- contact
`-- events
|-- training
|-- conference
`-- sprint
"""
self.portal = self.layer['portal']
self.blog = api.content.create(
type='Link', id='blog', container=self.portal)
self.about = api.content.create(
type='Folder', id='about', container=self.portal)
self.events = api.content.create(
type='Folder', id='events', container=self.portal)
self.team = api.content.create(
container=self.about, type='Document', id='team')
self.contact = api.content.create(
container=self.about, type='Document', id='contact')
self.training = api.content.create(
container=self.events, type='Event', id='training')
self.conference = api.content.create(
container=self.events, type='Event', id='conference')
self.sprint = api.content.create(
container=self.events, type='Event', id='sprint')
def test_create_constraints(self):
"""Test the constraints when creating content."""
from plone.api.exc import InvalidParameterError
from plone.api.exc import MissingParameterError
# This will definitely fail
with self.assertRaises(MissingParameterError):
api.content.create()
# Check the contraints for the type container
with self.assertRaises(MissingParameterError):
api.content.create(
type='Document',
id='test-doc',
)
# Check the contraints for the type parameter
container = mock.Mock()
with self.assertRaises(MissingParameterError):
api.content.create(
container=container,
id='test-doc',
)
# Check the contraints for id and title parameters
with self.assertRaises(MissingParameterError):
api.content.create(
container=container, type='Document'
)
# Check the contraints for allowed types in the container
container = self.events
with self.assertRaises(InvalidParameterError):
api.content.create(
container=container,
type='foo',
id='test-foo',
)
# Check the contraints for allowed types in the container if
# the container is the portal
container = self.portal
with self.assertRaises(InvalidParameterError) as cm:
api.content.create(
container=container,
type='foo',
id='test-foo'
)
# Check if the underlying error message is included
# in the InvalidParameterError message
self.assertIn(
"No such content type: foo",
cm.exception.message
)
# Check the contraints for allowed types in the container
# Create a folder
folder = api.content.create(
container=container, type='Folder', id='test-folder')
assert folder
# Constraint the allowed types
folder.setConstrainTypesMode(1)
folder.setLocallyAllowedTypes(('News Item',))
with self.assertRaises(InvalidParameterError):
api.content.create(
container=folder,
type='Document',
id='test-doc'
)
@unittest.skipUnless(
HAS_DEXTERITY,
"Only run when Dexterity is available.",
)
def test_create_dexterity(self):
"""Test create content based on Dexterity."""
container = self.portal
# Create a folder
folder = api.content.create(
container=container, type='Dexterity Folder', id='test-folder')
assert folder
self.assertEqual(folder.id, 'test-folder')
self.assertEqual(folder.portal_type, 'Dexterity Folder')
# Create an item
page = api.content.create(
container=folder, type='Dexterity Item', id='test-item')
assert page
self.assertEqual(page.id, 'test-item')
self.assertEqual(page.portal_type, 'Dexterity Item')
# Create an item with a title and without an id
page = api.content.create(
container=folder,
type='Dexterity Item',
title='Test id generated'
)
assert page
self.assertEqual(page.id, 'test-id-generated')
self.assertEqual(page.Title(), 'Test id generated')
self.assertEqual(page.portal_type, 'Dexterity Item')
# Try to create another item with same id, this should fail
with self.assertRaises(BadRequest):
api.content.create(
container=folder,
type='Dexterity Item',
id='test-item',
)
def test_create_archetypes(self):
"""Test creating content based on Archetypes."""
container = self.portal
# Create a folder
folder = api.content.create(
container=container, type='Folder', id='test-folder')
assert folder
self.assertEqual(folder.id, 'test-folder')
self.assertEqual(folder.portal_type, 'Folder')
# Create a document
page = api.content.create(
container=folder, type='Document', id='test-document')
assert page
self.assertEqual(page.id, 'test-document')
self.assertEqual(page.portal_type, 'Document')
# Create a document with a title and without an id
page = api.content.create(
container=folder, type='Document', title='Test id generated')
assert page
self.assertEqual(page.id, 'test-id-generated')
self.assertEqual(page.Title(), 'Test id generated')
self.assertEqual(page.portal_type, 'Document')
# Try to create another page with same id, this should fail
with self.assertRaises(BadRequest):
api.content.create(
container=folder,
type='Document',
id='test-document',
)
def test_create_with_safe_id(self):
"""Test the content creating with safe_id mode."""
container = self.portal
first_page = api.content.create(
container=container,
type='Document',
id='test-document',
safe_id=True,
)
assert first_page
self.assertEqual(first_page.id, 'test-document')
self.assertEqual(first_page.portal_type, 'Document')
# Second page is created with non-conflicting id
second_page = api.content.create(
container=container,
type='Document',
id='test-document',
safe_id=True,
)
assert second_page
self.assertEqual(second_page.id, 'test-document-1')
self.assertEqual(second_page.portal_type, 'Document')
def test_create_raises_unicodedecodeerror(self):
"""Test that the create method raises UnicodeDecodeErrors correctly."""
site = getGlobalSiteManager()
unicode_exception_message = "This is a fake unicode error"
# register a title indexer that will force a UnicodeDecodeError
# during content reindexing
@indexer(IContentish, IZCatalog)
def force_unicode_error(object):
raise UnicodeDecodeError('ascii', 'x', 1, 5,
unicode_exception_message)
site.registerAdapter(factory=force_unicode_error, name='Title')
def unregister_indexer():
site.unregisterAdapter(factory=force_unicode_error, name='Title')
self.addCleanup(unregister_indexer)
with self.assertRaises(UnicodeDecodeError) as ude:
api.content.create(
type='Folder', id='test-unicode-folder',
container=self.portal,
)
# check that the exception is the one we raised
self.assertEqual(ude.exception.reason, unicode_exception_message)
def test_get_constraints(self):
"""Test the constraints when content is fetched with get."""
# Path and UID parameter can not be given together
from plone.api.exc import InvalidParameterError
with self.assertRaises(InvalidParameterError):
api.content.get(
path='/',
UID='dummy'
)
# Either a path or UID must be given
from plone.api.exc import MissingParameterError
with self.assertRaises(MissingParameterError):
api.content.get()
def test_get(self):
"""Test the getting of content in varios ways."""
# Test getting the about folder by path and UID
about_by_path = api.content.get('/about')
about_by_uid = api.content.get(UID=self.about.UID())
self.assertEqual(self.about, about_by_path)
self.assertEqual(self.about, about_by_uid)
# Test getting the team document by path and UID
team_by_path = api.content.get('/about/team')
team_by_uid = api.content.get(UID=self.team.UID())
self.assertEqual(self.team, team_by_path)
self.assertEqual(self.team, team_by_uid)
# Test getting the team document by path that has portal id included
team_by_path = api.content.get(
'/{0}/about/team'.format(self.portal.getId()))
self.assertEqual(self.team, team_by_path)
# Test getting an non-existing item by path and UID
self.assertFalse(api.content.get('/spam/ham'))
self.assertFalse(api.content.get(UID='bacon'))
# Test getting a non-existing subfolder by path
self.assertFalse(api.content.get('/about/spam'))
def test_move_constraints(self):
"""Test the constraints for moving content."""
from plone.api.exc import MissingParameterError
# When no parameters are given an error is raised
with self.assertRaises(MissingParameterError):
api.content.move()
container = mock.Mock()
# Source is missing an should raise an error
with self.assertRaises(MissingParameterError):
api.content.move(source=container)
# Target is missing an should raise an error
with self.assertRaises(MissingParameterError):
api.content.move(target=container)
def test_move(self):
"""Test moving of content."""
container = self.portal
# Move contact to the same folder (basically a rename)
nucontact = api.content.move(source=self.contact, id='nu-contact')
assert (container['about']['nu-contact'] and
container['about']['nu-contact'] == nucontact)
assert 'contact' not in container['about'].keys()
# Move team page to portal root
team = api.content.move(source=self.team, target=container)
assert container['team'] and container['team'] == team
assert 'team' not in container['about'].keys()
# When moving objects we can change the id
team = container['team']
ourteam = api.content.move(source=team,
target=self.about,
id='our-team')
assert (container['about']['our-team'] and
container['about']['our-team'] == ourteam)
assert 'team' not in container.keys()
# Test with safe_id option when moving content
api.content.create(
container=self.about, type='Link', id='link-to-blog')
linktoblog1 = api.content.move(
source=self.blog,
target=self.about,
id='link-to-blog',
safe_id=True,
)
assert (container['about']['link-to-blog-1'] and
container['about']['link-to-blog-1'] == linktoblog1)
assert 'link-to-blog' not in container.keys()
api.content.move(source=self.conference, id='conference-renamed')
self.assertEqual(self.conference.id, 'conference-renamed')
# Move folderish object
about = api.content.move(source=container.about,
target=container.events)
assert (container['events']['about'] and
container['events']['about'] == about)
def test_rename_constraints(self):
"""Test the constraints for rename content."""
from plone.api.exc import MissingParameterError
# When no parameters are given an error is raised
with self.assertRaises(MissingParameterError):
api.content.rename()
container = mock.Mock()
# Source is missing an should raise an error
with self.assertRaises(MissingParameterError):
api.content.rename(obj=container)
def test_rename(self):
"""Test renaming of content."""
container = self.portal
# Rename contact
nucontact = api.content.rename(obj=self.contact, new_id='nu-contact')
assert (container['about']['nu-contact'] and
container['about']['nu-contact'] == nucontact)
assert 'contact' not in container['about'].keys()
# Test with safe_id option when moving content
api.content.create(
container=self.about, type='Link', id='link-to-blog')
linktoblog1 = api.content.rename(
obj=container['about']['link-to-blog'],
new_id='link-to-blog',
safe_id=True,
)
assert (container['about']['link-to-blog-1'] and
container['about']['link-to-blog-1'] == linktoblog1)
assert 'link-to-blog' not in container.keys()
# Rename to existing id
api.content.create(
container=self.about, type='Link', id='link-to-blog')
with self.assertRaises(CopyError):
api.content.rename(
obj=container['about']['link-to-blog'],
new_id='link-to-blog-1',
)
linktoblog11 = api.content.rename(
obj=container['about']['link-to-blog'],
new_id='link-to-blog-1',
safe_id=True,
)
assert (container['about']['link-to-blog-1-1'] and
container['about']['link-to-blog-1-1'] == linktoblog11)
assert 'link-to-blog' not in container.keys()
def test_copy_constraints(self):
"""Test the constraints for moving content."""
from plone.api.exc import MissingParameterError
# When no parameters are given an error is raised
with self.assertRaises(MissingParameterError):
api.content.copy()
container = mock.Mock()
# Source is missing and should raise an error
with self.assertRaises(MissingParameterError):
api.content.copy(source=container)
def test_copy(self):
"""Test the copying of content."""
container = self.portal
# Copy team page to portal root
team = api.content.copy(source=self.team, target=container)
assert container['team'] and container['team'] == team
assert (
container['about']['team'] and
container['about']['team'] != team
) # old content still available
# When copying objects we can change the id
ourteam = api.content.copy(source=self.team,
target=self.about,
id='our-team')
assert (container['about']['our-team'] and
container['about']['our-team'] == ourteam)
# When copying whithout target parameter should take source parent
api.content.copy(source=self.team, id='our-team-no-target')
assert container['about']['our-team-no-target']
# Test the safe_id option when moving content
api.content.create(
container=self.about, type='Link', id='link-to-blog')
linktoblog1 = api.content.copy(
source=self.blog,
target=self.about,
id='link-to-blog',
safe_id=True,
)
assert (container['about']['link-to-blog-1'] and
container['about']['link-to-blog-1'] == linktoblog1)
# Copy folderish content under target
about = api.content.copy(source=container.about,
target=container.events)
assert (container['events']['about'] and
container['events']['about'] == about)
def test_delete_constraints(self):
"""Test the constraints for deleting content."""
# When no parameters are given an error is raised
from plone.api.exc import MissingParameterError
with self.assertRaises(MissingParameterError):
api.content.delete()
def test_delete(self):
"""Test deleting a content item."""
container = self.portal
# The content item must be given as parameter
from plone.api.exc import MissingParameterError
with self.assertRaises(MissingParameterError):
api.content.delete()
# Delete the contact page
api.content.delete(self.contact)
assert 'contact' not in container['about'].keys()
def test_get_state(self):
"""Test retrieving the workflow state of a content item."""
# This should fail because an content item is mandatory
from plone.api.exc import MissingParameterError
with self.assertRaises(MissingParameterError):
api.content.get_state()
review_state = api.content.get_state(obj=self.blog)
self.assertEqual(review_state, 'private')
def test_transition(self):
"""Test transitioning the workflow state on a content item."""
from plone.api.exc import InvalidParameterError
from plone.api.exc import MissingParameterError
with self.assertRaises(MissingParameterError):
api.content.transition()
with self.assertRaises(MissingParameterError):
api.content.transition(obj=mock.Mock())
with self.assertRaises(MissingParameterError):
api.content.transition(transition='publish')
api.content.transition(obj=self.blog, transition='publish')
review_state = api.content.get_state(obj=self.blog)
self.assertEqual(review_state, 'published')
# This should fail because the transition doesn't exist
with self.assertRaises(InvalidParameterError) as cm:
api.content.transition(
transition='foo', obj=self.blog)
self.maxDiff = None # to see assert diff
self.assertMultiLineEqual(
str(cm.exception),
"Invalid transition 'foo'.\n"
"Valid transitions are:\n"
"reject\n"
"retract"
)
def test_get_view_constraints(self):
"""Test the constraints for deleting content."""
from plone.api.exc import MissingParameterError
request = self.layer['request']
# When no parameters are given an error is raised
with self.assertRaises(MissingParameterError):
api.content.get_view()
# name is required
with self.assertRaises(MissingParameterError):
api.content.get_view(
context=self.blog,
request=request,
)
# context is required
with self.assertRaises(MissingParameterError):
api.content.get_view(
name='plone',
request=request,
)
# request is required
with self.assertRaises(MissingParameterError):
api.content.get_view(
name='plone',
context=self.blog,
)
def test_get_view(self):
"""Test the view."""
request = self.layer['request']
view = api.content.get_view(
name='plone',
context=self.blog,
request=request,
)
self.assertEqual(aq_base(view.context), aq_base(self.blog))
self.assertEqual(view.__name__, 'plone')
self.assertTrue(hasattr(view, 'getIcon'))
# Try another standard view.
view = api.content.get_view(
name='plone_context_state',
context=self.blog,
request=request,
)
self.assertEqual(view.__name__, 'plone_context_state')
self.assertEqual(aq_base(view.canonical_object()), aq_base(self.blog))
def test_get_uuid(self):
"""Test getting a content item's UUID."""
from plone.api.exc import MissingParameterError
container = self.portal
# The content item must be given as parameter
with self.assertRaises(MissingParameterError):
api.content.get_uuid()
generator = getUtility(IUUIDGenerator)
# Set the UUID and compare it with the one we get from our function
# Dexterity
container.invokeFactory('Dexterity Item', 'test-dexterity')
item = container['test-dexterity']
uuid1 = generator()
IMutableUUID(item).set(uuid1)
uuid2 = api.content.get_uuid(item)
self.assertEqual(uuid1, uuid2)
self.assertIsInstance(uuid2, str)
# Archetypes
container.invokeFactory('Document', 'test-archetype')
document = container['test-archetype']
uuid1 = generator()
document._setUID(uuid1)
uuid2 = api.content.get_uuid(document)
self.assertEqual(uuid1, uuid2)
self.assertIsInstance(uuid2, str)
def test_get_view_view_not_found(self):
"""Test that error msg lists available views if a view is not found."""
request = self.layer['request']
from plone.api.exc import InvalidParameterError
with self.assertRaises(InvalidParameterError) as cm:
api.content.get_view(
name='foo',
context=self.blog,
request=request
)
self.maxDiff = None # to see assert diff
self.assertTrue(
str(cm.exception).startswith(
"Cannot find a view with name 'foo'.\n"
"Available views are:\n"
'\n'
)
)
# This is just a sampling of views that should be present.
# Test against only these rather than the full list. Otherwise, this
# test has to maintain an up-to-date list of every view in Plone.
should_be_theres = (
"adapter",
"authenticator",
"checkDocument",
"get_macros",
"history",
"plone",
"plone_tools",
"resource",
"search",
"sharing",
"skin",
"text-transform",
"uuid",
"view",
)
for should_be_there in should_be_theres:
self.assertIn((should_be_there + '\n'), str(cm.exception))