Merge branch 'master' into wip-flask-one
This commit is contained in:
@@ -33,18 +33,57 @@ log = logging.getLogger(__name__)
|
|||||||
def shortcode(name: str):
|
def shortcode(name: str):
|
||||||
"""Class decorator for shortcodes."""
|
"""Class decorator for shortcodes."""
|
||||||
|
|
||||||
def decorator(cls):
|
def decorator(decorated):
|
||||||
assert hasattr(cls, '__call__'), '@shortcode should be used on callables.'
|
assert hasattr(decorated, '__call__'), '@shortcode should be used on callables.'
|
||||||
if isinstance(cls, type):
|
if isinstance(decorated, type):
|
||||||
instance = cls()
|
as_callable = decorated()
|
||||||
else:
|
else:
|
||||||
instance = cls
|
as_callable = decorated
|
||||||
shortcodes.register(name)(instance)
|
shortcodes.register(name)(as_callable)
|
||||||
return cls
|
return decorated
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class capcheck:
|
||||||
|
"""Decorator for shortcodes.
|
||||||
|
|
||||||
|
On call, check for capabilities before calling the function. If the user does not
|
||||||
|
have a capability, display a message insdead of the content.
|
||||||
|
|
||||||
|
kwargs:
|
||||||
|
- 'cap': Capability required for viewing.
|
||||||
|
- 'nocap': Optional, text shown when the user does not have this capability.
|
||||||
|
- others: Passed to the decorated shortcode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, decorated):
|
||||||
|
assert hasattr(decorated, '__call__'), '@capcheck should be used on callables.'
|
||||||
|
if isinstance(decorated, type):
|
||||||
|
as_callable = decorated()
|
||||||
|
else:
|
||||||
|
as_callable = decorated
|
||||||
|
self.decorated = as_callable
|
||||||
|
|
||||||
|
def __call__(self,
|
||||||
|
context: typing.Any,
|
||||||
|
content: str,
|
||||||
|
pargs: typing.List[str],
|
||||||
|
kwargs: typing.Dict[str, str]) -> str:
|
||||||
|
from pillar.auth import current_user
|
||||||
|
|
||||||
|
cap = kwargs.pop('cap', '')
|
||||||
|
if cap:
|
||||||
|
nocap = kwargs.pop('nocap', '')
|
||||||
|
if not current_user.has_cap(cap):
|
||||||
|
if not nocap:
|
||||||
|
return ''
|
||||||
|
html = html_module.escape(nocap)
|
||||||
|
return f'<p class="shortcode nocap">{html}</p>'
|
||||||
|
|
||||||
|
return self.decorated(context, content, pargs, kwargs)
|
||||||
|
|
||||||
|
|
||||||
@shortcode('test')
|
@shortcode('test')
|
||||||
class Test:
|
class Test:
|
||||||
def __call__(self,
|
def __call__(self,
|
||||||
@@ -68,6 +107,7 @@ class Test:
|
|||||||
|
|
||||||
|
|
||||||
@shortcode('youtube')
|
@shortcode('youtube')
|
||||||
|
@capcheck
|
||||||
class YouTube:
|
class YouTube:
|
||||||
log = log.getChild('YouTube')
|
log = log.getChild('YouTube')
|
||||||
|
|
||||||
@@ -129,6 +169,7 @@ class YouTube:
|
|||||||
|
|
||||||
|
|
||||||
@shortcode('iframe')
|
@shortcode('iframe')
|
||||||
|
@capcheck
|
||||||
def iframe(context: typing.Any,
|
def iframe(context: typing.Any,
|
||||||
content: str,
|
content: str,
|
||||||
pargs: typing.List[str],
|
pargs: typing.List[str],
|
||||||
@@ -140,16 +181,6 @@ def iframe(context: typing.Any,
|
|||||||
- others: Turned into attributes for the iframe element.
|
- others: Turned into attributes for the iframe element.
|
||||||
"""
|
"""
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from pillar.auth import current_user
|
|
||||||
|
|
||||||
cap = kwargs.pop('cap', '')
|
|
||||||
if cap:
|
|
||||||
nocap = kwargs.pop('nocap', '')
|
|
||||||
if not current_user.has_cap(cap):
|
|
||||||
if not nocap:
|
|
||||||
return ''
|
|
||||||
html = html_module.escape(nocap)
|
|
||||||
return f'<p class="shortcode nocap">{html}</p>'
|
|
||||||
|
|
||||||
kwargs['class'] = f'shortcode {kwargs.get("class", "")}'.strip()
|
kwargs['class'] = f'shortcode {kwargs.get("class", "")}'.strip()
|
||||||
element = ET.Element('iframe', kwargs)
|
element = ET.Element('iframe', kwargs)
|
||||||
|
170
tests/test_api/test_cerberus.py
Normal file
170
tests/test_api/test_cerberus.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"""Test that what we feed to Cerberus actually works.
|
||||||
|
|
||||||
|
This'll help us upgrade to new versions of Cerberus.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from pillar.tests import AbstractPillarTest
|
||||||
|
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
|
|
||||||
|
class CerberusCanaryTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def _canary_test(self, validator):
|
||||||
|
groups_schema = {'name': {'type': 'string', 'required': True}}
|
||||||
|
|
||||||
|
# On error, validate_schema() raises ValidationError
|
||||||
|
validator.validate_schema(groups_schema)
|
||||||
|
|
||||||
|
# On error, validate() returns False
|
||||||
|
self.assertTrue(validator.validate({'name': 'je moeder'}, groups_schema))
|
||||||
|
self.assertFalse(validator.validate({'je moeder': 'op je hoofd'}, groups_schema))
|
||||||
|
|
||||||
|
def test_canary(self):
|
||||||
|
import cerberus
|
||||||
|
|
||||||
|
validator = cerberus.Validator()
|
||||||
|
self._canary_test(validator)
|
||||||
|
|
||||||
|
def test_our_validator_simple(self):
|
||||||
|
from pillar.api import custom_field_validation
|
||||||
|
|
||||||
|
validator = custom_field_validation.ValidateCustomFields()
|
||||||
|
self._canary_test(validator)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationTest(AbstractPillarTest):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
from pillar.api import custom_field_validation
|
||||||
|
|
||||||
|
self.validator = custom_field_validation.ValidateCustomFields()
|
||||||
|
self.user_id = ObjectId(8 * 'abc')
|
||||||
|
self.ensure_user_exists(self.user_id, 'Tést Üsâh')
|
||||||
|
|
||||||
|
def assertValid(self, document, schema):
|
||||||
|
with self.app.app_context():
|
||||||
|
is_valid = self.validator.validate(document, schema)
|
||||||
|
self.assertTrue(is_valid, f'errors: {self.validator.errors}')
|
||||||
|
|
||||||
|
def assertInvalid(self, document, schema):
|
||||||
|
with self.app.app_context():
|
||||||
|
is_valid = self.validator.validate(document, schema)
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectValidationTest(ValidationTest):
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
from pillar.api.eve_settings import projects_schema
|
||||||
|
self.assertInvalid({}, projects_schema)
|
||||||
|
|
||||||
|
def test_simple_project(self):
|
||||||
|
from pillar.api.eve_settings import projects_schema
|
||||||
|
|
||||||
|
project = {
|
||||||
|
'name': 'Té Ærhüs',
|
||||||
|
'user': self.user_id,
|
||||||
|
'category': 'assets',
|
||||||
|
'is_private': False,
|
||||||
|
'status': 'published',
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertValid(project, projects_schema)
|
||||||
|
|
||||||
|
def test_with_node_types(self):
|
||||||
|
from pillar.api.eve_settings import projects_schema
|
||||||
|
from pillar.api import node_types
|
||||||
|
|
||||||
|
project = {
|
||||||
|
'name': 'Té Ærhüs',
|
||||||
|
'user': self.user_id,
|
||||||
|
'category': 'assets',
|
||||||
|
'is_private': False,
|
||||||
|
'status': 'published',
|
||||||
|
'node_types': [node_types.node_type_asset,
|
||||||
|
node_types.node_type_comment]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertValid(project, projects_schema)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeValidationTest(ValidationTest):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.pid, self.project = self.ensure_project_exists()
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
from pillar.api.eve_settings import nodes_schema
|
||||||
|
self.assertInvalid({}, nodes_schema)
|
||||||
|
|
||||||
|
def test_asset(self):
|
||||||
|
from pillar.api.eve_settings import nodes_schema
|
||||||
|
|
||||||
|
file_id, _ = self.ensure_file_exists()
|
||||||
|
|
||||||
|
node = {
|
||||||
|
'name': '"The Harmless Prototype™"',
|
||||||
|
'project': self.pid,
|
||||||
|
'node_type': 'asset',
|
||||||
|
'properties': {
|
||||||
|
'status': 'published',
|
||||||
|
'content_type': 'image',
|
||||||
|
'file': file_id,
|
||||||
|
},
|
||||||
|
'user': self.user_id,
|
||||||
|
'short_code': 'ABC333',
|
||||||
|
}
|
||||||
|
self.assertValid(node, nodes_schema)
|
||||||
|
|
||||||
|
def test_asset_invalid_properties(self):
|
||||||
|
from pillar.api.eve_settings import nodes_schema
|
||||||
|
|
||||||
|
file_id, _ = self.ensure_file_exists()
|
||||||
|
|
||||||
|
node = {
|
||||||
|
'name': '"The Harmless Prototype™"',
|
||||||
|
'project': self.pid,
|
||||||
|
'node_type': 'asset',
|
||||||
|
'properties': {
|
||||||
|
'status': 'invalid-status',
|
||||||
|
'content_type': 'image',
|
||||||
|
'file': file_id,
|
||||||
|
},
|
||||||
|
'user': self.user_id,
|
||||||
|
'short_code': 'ABC333',
|
||||||
|
}
|
||||||
|
self.assertInvalid(node, nodes_schema)
|
||||||
|
|
||||||
|
def test_comment(self):
|
||||||
|
from pillar.api.eve_settings import nodes_schema
|
||||||
|
|
||||||
|
file_id, _ = self.ensure_file_exists()
|
||||||
|
|
||||||
|
node = {
|
||||||
|
'name': '"The Harmless Prototype™"',
|
||||||
|
'project': self.pid,
|
||||||
|
'node_type': 'asset',
|
||||||
|
'properties': {
|
||||||
|
'status': 'published',
|
||||||
|
'content_type': 'image',
|
||||||
|
'file': file_id,
|
||||||
|
},
|
||||||
|
'user': self.user_id,
|
||||||
|
'short_code': 'ABC333',
|
||||||
|
}
|
||||||
|
node_id = self.create_node(node)
|
||||||
|
|
||||||
|
comment = {
|
||||||
|
'name': 'comment on some node',
|
||||||
|
'project': self.pid,
|
||||||
|
'node_type': 'comment',
|
||||||
|
'properties': {
|
||||||
|
'content': 'this is a comment',
|
||||||
|
'status': 'published',
|
||||||
|
},
|
||||||
|
'parent': node_id,
|
||||||
|
}
|
||||||
|
self.assertValid(comment, nodes_schema)
|
@@ -40,7 +40,7 @@ class DemoTest(unittest.TestCase):
|
|||||||
self.assertEqual('<dl><dt>test</dt><dt>ü</dt><dd>é</dd></dl>', render('{test ü="é"}'))
|
self.assertEqual('<dl><dt>test</dt><dt>ü</dt><dd>é</dd></dl>', render('{test ü="é"}'))
|
||||||
|
|
||||||
|
|
||||||
class YouTubeTest(unittest.TestCase):
|
class YouTubeTest(AbstractPillarTest):
|
||||||
def test_missing(self):
|
def test_missing(self):
|
||||||
from pillar.shortcodes import render
|
from pillar.shortcodes import render
|
||||||
|
|
||||||
@@ -104,6 +104,19 @@ class YouTubeTest(unittest.TestCase):
|
|||||||
render('{youtube "https://www.youtube.com/watch?v=NwVGvcIrNWA" width=5 height="3"}')
|
render('{youtube "https://www.youtube.com/watch?v=NwVGvcIrNWA" width=5 height="3"}')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_user_no_cap(self):
|
||||||
|
from pillar.shortcodes import render
|
||||||
|
|
||||||
|
with self.app.app_context():
|
||||||
|
# Anonymous user, so no subscriber capability.
|
||||||
|
self.assertEqual('', render('{youtube ABCDEF cap=subscriber}'))
|
||||||
|
self.assertEqual('', render('{youtube ABCDEF cap="subscriber"}'))
|
||||||
|
self.assertEqual(
|
||||||
|
'<p class="shortcode nocap">Aðeins áskrifendur hafa aðgang að þessu efni.</p>',
|
||||||
|
render('{youtube ABCDEF'
|
||||||
|
' cap="subscriber"'
|
||||||
|
' nocap="Aðeins áskrifendur hafa aðgang að þessu efni."}'))
|
||||||
|
|
||||||
|
|
||||||
class IFrameTest(AbstractPillarTest):
|
class IFrameTest(AbstractPillarTest):
|
||||||
def test_missing_cap(self):
|
def test_missing_cap(self):
|
||||||
|
Reference in New Issue
Block a user