pillar/tests/test_web/test_nodes.py
Sybren A. Stüvel 4499f911de Node breadcrumbs
Breadcrumbs are served as JSON at `/nodes/{node ID}/breadcrumbs`, with
the top-level parent listed first and the node itself listed last:

    {breadcrumbs: [
        ...
        {_id: "parentID",
         name: "The Parent Node",
         node_type: "group",
         url: "/p/project/parentID"},
        {_id: "deadbeefbeefbeefbeeffeee",
         name: "The Node Itself",
         node_type: "asset",
         url: "/p/project/nodeID",
         _self: true},
    ]}

When a parent node is missing, it has a breadcrumb like this:

    {_id: "deadbeefbeefbeefbeeffeee",
     _exists': false,
     name': '-unknown-'}

Of course this will be the first in the breadcrumbs list, as we won't be
able to determine the parent of a deleted/non-existing node.

Breadcrumbs are rendered with Vue.js in Blender Cloud (not in Pillar);
see projects/view.pug.
2019-03-28 12:40:33 +01:00

132 lines
4.5 KiB
Python

import typing
from bson import ObjectId
import flask
from pillar.tests import AbstractPillarTest
class BreadcrumbsTest(AbstractPillarTest):
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.project_id, self.project = self.ensure_project_exists()
def _create_group(self,
parent_id: typing.Optional[ObjectId],
name: str) -> ObjectId:
node = {
'name': name,
'description': '',
'node_type': 'group',
'user': self.project['user'],
'properties': {'status': 'published'},
'project': self.project_id,
}
if parent_id:
node['parent'] = parent_id
return self.create_node(node)
def test_happy(self) -> ObjectId:
# Create the nodes we expect to be returned in the breadcrumbs.
top_group_node_id = self._create_group(None, 'Top-level node')
group_node_id = self._create_group(top_group_node_id, 'Group node')
fid, _ = self.ensure_file_exists()
node_id = self.create_node({
'name': 'Asset node',
'parent': group_node_id,
'description': '',
'node_type': 'asset',
'user': self.project['user'],
'properties': {'status': 'published', 'file': fid},
'project': self.project_id,
})
# Create some siblings that should not be returned.
self._create_group(None, 'Sibling of top node')
self._create_group(top_group_node_id, 'Sibling of group node')
self._create_group(group_node_id, 'Sibling of asset node')
expected = {'breadcrumbs': [
{'_id': str(top_group_node_id),
'name': 'Top-level node',
'node_type': 'group',
'url': f'/p/{self.project["url"]}/{top_group_node_id}'},
{'_id': str(group_node_id),
'name': 'Group node',
'node_type': 'group',
'url': f'/p/{self.project["url"]}/{group_node_id}'},
{'_id': str(node_id),
'_self': True,
'name': 'Asset node',
'node_type': 'asset',
'url': f'/p/{self.project["url"]}/{node_id}'},
]}
with self.app.app_context():
url = flask.url_for('nodes.breadcrumbs', node_id=str(node_id))
actual = self.get(url).json
self.assertEqual(expected, actual)
return node_id
def test_missing_parent(self):
# Note that this group node doesn't exist in the database:
group_node_id = ObjectId(3 * 'deadbeef')
fid, _ = self.ensure_file_exists()
node_id = self.create_node({
'name': 'Asset node',
'parent': group_node_id,
'description': '',
'node_type': 'asset',
'user': self.project['user'],
'properties': {'status': 'published', 'file': fid},
'project': self.project_id,
})
expected = {'breadcrumbs': [
{'_id': str(group_node_id),
'_exists': False,
'name': '-unknown-'},
{'_id': str(node_id),
'_self': True,
'name': 'Asset node',
'node_type': 'asset',
'url': f'/p/{self.project["url"]}/{node_id}'},
]}
with self.app.app_context():
url = flask.url_for('nodes.breadcrumbs', node_id=str(node_id))
actual = self.get(url).json
self.assertEqual(expected, actual)
def test_missing_node(self):
with self.app.app_context():
url = flask.url_for('nodes.breadcrumbs', node_id=3 * 'deadbeef')
self.get(url, expected_status=404)
def test_permissions(self):
# Use the same test case as the happy case.
node_id = self.test_happy()
# Tweak the project to make it private.
with self.app.app_context():
proj_coll = self.app.db('projects')
result = proj_coll.update_one({'_id': self.project_id},
{'$set': {'permissions.world': []}})
self.assertEqual(1, result.modified_count)
self.project = self.fetch_project_from_db(self.project_id)
with self.app.app_context():
url = flask.url_for('nodes.breadcrumbs', node_id=str(node_id))
# Anonymous access should be forbidden.
self.get(url, expected_status=403)
# Authorized access should work, though.
self.create_valid_auth_token(self.project['user'], token='user-token')
self.get(url, auth_token='user-token')