Add some tests for checking private story behaviour

This adds some tests which verify the behaviour of private stories is as
intended, and that they are correctly filtered out of any related
results such as tasks and comments.

Some changes were necessary in mock_data.py to account for this. The
session used to insert data is now shared for all the test objects,
rather than creating a new session per object type. This makes it easier
to query the created data to populate the various many-to-many
relationships in the StoryBoard data model, to allow for testing of
things which require permissions to be created and populated.

Change-Id: Ib4db5b3d07363d3bdc97ad825a12f8882a0aa8f1
This commit is contained in:
Adam Coldrick 2019-03-19 12:43:34 +00:00
parent 6cdc6f4bfb
commit 0207e1704b
5 changed files with 220 additions and 37 deletions

View File

@ -36,6 +36,24 @@ class TestComments(base.FunctionalTest):
response = self.get_json(self.comments_resource % self.story_id)
self.assertEqual(0, len(response))
def test_comments_privacy(self):
url = '/stories/6/comments'
response = self.get_json(url, expect_errors=True)
self.assertEqual(200, response.status_code)
self.assertEqual(1, len(response.json))
# The user with token `valid_user_token` can't see the story, and
# so shouldn't be able to see the comment
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(url, headers=headers, expect_errors=True)
self.assertEqual(0, len(response.json))
# Unauthenticated users shouldn't be able to see anything in private
# stories
self.default_headers.pop('Authorization')
response = self.get_json(url, expect_errors=True)
self.assertEqual(0, len(response.json))
def test_create(self):
self.post_json(self.comments_resource % self.story_id, self.comment_01)
self.post_json(self.comments_resource % self.story_id, self.comment_02)

View File

@ -33,7 +33,29 @@ class TestStories(base.FunctionalTest):
def test_stories_endpoint(self):
response = self.get_json(self.resource)
self.assertEqual(5, len(response))
self.assertEqual(6, len(response))
def test_private_story_visibility(self):
url = self.resource + '/6'
story = self.get_json(url)
# User with token `valid_superuser_token` has permission to see
# the story, so should be able to get it without issue.
self.assertEqual(story['title'], 'Test Private Story')
self.assertTrue(story['private'])
self.assertEqual(1, len(story['users']))
self.assertEqual('Super User', story['users'][0]['full_name'])
self.assertEqual(0, len(story['teams']))
# User with token `valid_user_token` doesn't have permission
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(url, headers=headers, expect_errors=True)
self.assertEqual(404, response.status_code)
# Unauthenticated users shouldn't be able to see private stories
self.default_headers.pop('Authorization')
response = self.get_json(url, expect_errors=True)
self.assertEqual(404, response.status_code)
def test_create(self):
response = self.post_json(self.resource, self.story_01)
@ -62,22 +84,22 @@ class TestStories(base.FunctionalTest):
self.assertEqual(story['story_type_id'],
created_story['story_type_id'])
@unittest.skip("vulnerabilities are not supported.")
def test_create_private_vulnerability(self):
def test_create_private_story(self):
story = {
'title': 'StoryBoard',
'description': 'Awesome Task Tracker',
'story_type_id': 3
'private': True,
'users': [{'id': 1}]
}
response = self.post_json(self.resource, story)
created_story = response.json
self.assertEqual(story['title'], created_story['title'])
self.assertEqual(story['description'], created_story['description'])
self.assertEqual(story['story_type_id'],
created_story['story_type_id'])
self.assertEqual(story['private'],
created_story['private'])
@unittest.skip("vulnerabilities are not supported.")
@unittest.skip("public vulnerabilities are not supported.")
def test_create_public_vulnerability(self):
story = {
'title': 'StoryBoard',
@ -129,25 +151,31 @@ class TestStories(base.FunctionalTest):
{'story_type_id': story_type_id})
self.assertEqual(story_type_id, response.json['story_type_id'])
@unittest.skip("vulnerabilities are not supported.")
def test_update_private_to_public_vulnerability(self):
def test_update_private_to_public(self):
story = {
'title': 'StoryBoard',
'description': 'Awesome Task Tracker',
'story_type_id': 3
'private': True
}
response = self.post_json(self.resource, story)
created_story = response.json
self.assertEqual(story["story_type_id"],
created_story["story_type_id"])
self.assertEqual(story['private'],
created_story['private'])
response = self.put_json(self.resource +
('/%s' % created_story["id"]),
{'story_type_id': 4})
created_story = response.json
self.assertEqual(4, created_story['story_type_id'])
('/%s' % created_story['id']),
{'private': False})
updated_story = response.json
self.assertFalse(updated_story['private'])
# Check that a different user can see the story
headers = {'Authorization': 'Bearer valid_user_token'}
api_story = self.get_json(self.resource + '/%s' % created_story['id'],
headers=headers)
self.assertEqual(story['title'], api_story['title'])
self.assertEqual(story['description'], api_story['description'])
def test_update_restricted_branches(self):
response = self.put_json(self.resource + '/1', {'story_type_id': 2},

View File

@ -107,7 +107,34 @@ class TestTasksPrimary(base.FunctionalTest):
def test_tasks_endpoint(self):
response = self.get_json(self.resource)
self.assertEqual(5, len(response))
# Check that tasks in private stories are correctly filtered
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(self.resource, headers=headers)
self.assertEqual(4, len(response))
self.default_headers.pop('Authorization')
response = self.get_json(self.resource)
self.assertEqual(4, len(response))
def test_private_task_visibility(self):
url = self.resource + '/5'
# Task with id 5 is in a private story which the user with token
# `valid_superuser_token` can see
response = self.get_json(url)
self.assertEqual('Task in private story', response['title'])
# The user with token `valid_user_token` can't see the story, and
# so shouldn't be able to see the task
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(url, headers=headers, expect_errors=True)
self.assertEqual(404, response.status_code)
# Unauthenticated users shouldn't be able to see anything in private
# stories
self.default_headers.pop('Authorization')
response = self.get_json(url, expect_errors=True)
self.assertEqual(404, response.status_code)
def test_create(self):
result = self.post_json(self.resource, self.task_01)
@ -274,6 +301,38 @@ class TestTasksNestedController(base.FunctionalTest):
self.assertEqual(400, response.status_code)
def test_tasks_endpoint_privacy(self):
self.resource = '/stories/6/tasks'
response = self.get_json(self.resource)
self.assertEqual(1, len(response))
# Check that tasks in private stories are correctly filtered
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(self.resource, headers=headers)
self.assertEqual(0, len(response))
self.default_headers.pop('Authorization')
response = self.get_json(self.resource)
self.assertEqual(0, len(response))
def test_private_task_visibility(self):
url = '/stories/6/tasks/5'
# Task with id 5 is in a private story which the user with token
# `valid_superuser_token` can see
response = self.get_json(url)
self.assertEqual('Task in private story', response['title'])
# The user with token `valid_user_token` can't see the story, and
# so shouldn't be able to see the task
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(url, headers=headers, expect_errors=True)
self.assertEqual(404, response.status_code)
# Unauthenticated users shouldn't be able to see anything in private
# stories
self.default_headers.pop('Authorization')
response = self.get_json(url, expect_errors=True)
self.assertEqual(404, response.status_code)
def test_create(self):
result = self.post_json(self.resource, {
'title': 'StoryBoard',

View File

@ -18,6 +18,10 @@ from storyboard.tests import base
class TestTimelineEvents(base.FunctionalTest):
def setUp(self):
super(TestTimelineEvents, self).setUp()
self.default_headers['Authorization'] = 'Bearer valid_superuser_token'
def test_get_all_events(self):
"""Assert that we can retrieve a list of events from a story."""
@ -27,6 +31,26 @@ class TestTimelineEvents(base.FunctionalTest):
self.assertEqual(200, response.status_code)
self.assertEqual(3, len(response.json))
def test_get_all_events_privacy(self):
"""Assert that events for private stories are access controlled."""
url = '/stories/6/events'
response = self.get_json(url, expect_errors=True)
self.assertEqual(200, response.status_code)
self.assertEqual(2, len(response.json))
# The user with token `valid_user_token` can't see the story, and
# so shouldn't be able to see the events
headers = {'Authorization': 'Bearer valid_user_token'}
response = self.get_json(url, headers=headers, expect_errors=True)
self.assertEqual(0, len(response.json))
# Unauthenticated users shouldn't be able to see anything in private
# stories
self.default_headers.pop('Authorization')
response = self.get_json(url, expect_errors=True)
self.assertEqual(0, len(response.json))
def test_filter_by_event_type(self):
"""Assert that we can correctly filter an event by event type."""
response = self.get_json('/stories/1/events?event_type=story_created'

View File

@ -21,6 +21,7 @@ from storyboard.db.models import AccessToken
from storyboard.db.models import Branch
from storyboard.db.models import Comment
from storyboard.db.models import Milestone
from storyboard.db.models import Permission
from storyboard.db.models import Project
from storyboard.db.models import ProjectGroup
from storyboard.db.models import Story
@ -36,6 +37,7 @@ def load():
"""Load a batch of useful data into the database that our tests can work
with.
"""
session = db.get_session(autocommit=False, in_request=False)
now = datetime.datetime.now(tz=pytz.utc)
expires_at = now + datetime.timedelta(seconds=3600)
expired_at = now + datetime.timedelta(seconds=-3600)
@ -57,7 +59,8 @@ def load():
openid='otheruser_openid',
full_name='Other User',
is_superuser=False)
])
], session)
users = session.query(User).all()
# Load some preferences for the above users.
load_data([
@ -86,7 +89,7 @@ def load():
key='plugin_email_digest',
value='False',
type='bool'),
])
], session)
# Load a variety of sensibly named access tokens.
load_data([
@ -110,7 +113,7 @@ def load():
access_token='expired_user_token',
expires_in=3600,
expires_at=expired_at)
])
], session)
# Create some test projects.
projects = load_data([
@ -126,7 +129,7 @@ def load():
id=3,
name='tests/project3',
description='Project 1 Description - foo')
])
], session)
# Create some test project groups.
load_data([
@ -153,7 +156,17 @@ def load():
name='projectgroup3',
title='A Sort - foo'
)
])
], session)
# Create some permissions
load_data([
Permission(
name='view_story_6',
codename='view_story',
users=[users[0]]
)
], session)
permissions = session.query(Permission).all()
# Create some stories.
load_data([
@ -181,8 +194,15 @@ def load():
id=5,
title="A Test story 5 - oh hai",
description="Test Description - oh hai"
),
Story(
id=6,
title="Test Private Story",
description="For Super User's eyes only",
private=True,
permissions=[permissions[0]]
)
])
], session)
# Create some tasks
load_data([
@ -229,8 +249,19 @@ def load():
branch_id=2,
assignee_id=1,
priority='medium'
),
Task(
id=5,
creator_id=1,
title='Task in private story',
status='todo',
story_id=6,
project_id=2,
branch_id=2,
assignee_id=1,
priority='medium'
)
])
], session)
# Generate some timeline events for the above stories.
load_data([
@ -280,28 +311,48 @@ def load():
'"old_assignee_id": null, '
'"task_id": 1, '
'"new_assignee_id": 2}'
),
TimeLineEvent(
id=7,
story_id=6,
author_id=1,
event_type=event.STORY_CREATED,
event_info='{"story_id": 6, '
'"story_title": "Test Private Story"}'
)
])
], session)
# Create a comment.
# Create some comments.
load_data([
Comment(
id=1,
content="Test Comment",
is_active=True
),
Comment(
id=2,
content="Comment on a private story",
is_active=True
)
])
], session)
# Create a timeline event for the above comment.
# Create timeline events for the above comments.
load_data([
TimeLineEvent(
id=7,
id=8,
story_id=1,
comment_id=1,
author_id=1,
event_type=event.USER_COMMENT
),
TimeLineEvent(
id=9,
story_id=6,
comment_id=2,
author_id=1,
event_type=event.USER_COMMENT
)
])
], session)
# Load some subscriptions.
load_data([
@ -323,7 +374,7 @@ def load():
target_type='story',
target_id=1
),
])
], session)
# Load some branches
load_data([
@ -345,7 +396,7 @@ def load():
name='master',
restricted=True
)
])
], session)
# Load some milestones
load_data([
@ -359,27 +410,30 @@ def load():
name='test_milestone_02',
branch_id=2
)
])
], session)
# Load some teams
load_data([
Team(
id=1,
name='test_team_1'
name='test_team_1',
users=[users[0]]
),
Team(
id=2,
name='test_team_2'
name='test_team_2',
users=users[1:]
)
])
], session)
def load_data(data):
def load_data(data, session=None):
"""Pre load test data into the database.
:param data An iterable collection of database models.
"""
session = db.get_session(autocommit=False, in_request=False)
if session is None:
session = db.get_session(autocommit=False, in_request=False)
for entity in data:
session.add(entity)