Page MenuHome

test_shots.py
No OneTemporary

File Metadata

Created
Fri, Sep 20, 12:35 PM

test_shots.py

# -*- encoding: utf-8 -*-
import unittest
import responses
from bson import ObjectId
import pillarsdk
import pillarsdk.exceptions as sdk_exceptions
import pillar.tests
import pillar.auth
import pillar.tests.common_test_data as ctd
from abstract_attract_test import AbstractAttractTest
class AbstractShotTest(AbstractAttractTest):
def setUp(self, **kwargs):
AbstractAttractTest.setUp(self, **kwargs)
self.tmngr = self.attract.task_manager
self.smngr = self.attract.shot_manager
self.proj_id, self.project = self.ensure_project_exists()
self.sdk_project = pillarsdk.Project(pillar.tests.mongo_to_sdk(self.project))
def create_task(self, shot_id, task_type):
with self.app.test_request_context():
# Log in as project admin user
pillar.auth.login_user(ctd.EXAMPLE_PROJECT_OWNER_ID)
self.mock_blenderid_validate_happy()
task = self.tmngr.create_task(self.sdk_project, parent=shot_id, task_type=task_type)
self.assertIsInstance(task, pillarsdk.Node)
return task
def create_shot(self):
with self.app.test_request_context():
# Log in as project admin user
pillar.auth.login_user(ctd.EXAMPLE_PROJECT_OWNER_ID)
self.mock_blenderid_validate_happy()
shot = self.smngr.create_shot(self.sdk_project)
self.assertIsInstance(shot, pillarsdk.Node)
return shot
class ShotManagerTest(AbstractShotTest):
@responses.activate
def test_tasks_for_shot(self):
shot1 = self.create_shot()
shot2 = self.create_shot()
shot1_id = shot1['_id']
shot2_id = shot2['_id']
task1 = self.create_task(shot1_id, 'fx')
task2 = self.create_task(shot1_id, 'fx')
task3 = self.create_task(shot1_id, 'høken')
task4 = self.create_task(shot2_id, 'effects')
task5 = self.create_task(shot2_id, 'effects')
task6 = self.create_task(shot2_id, 'ïnžane')
with self.app.test_request_context():
# Log in as project admin user
pillar.auth.login_user(ctd.EXAMPLE_PROJECT_OWNER_ID)
self.mock_blenderid_validate_happy()
shot_id_to_task = self.smngr.tasks_for_nodes([shot1, shot2],
['fx', 'høken', 'effects'])
# Just test based on task IDs, as strings are turned into datetimes etc. by the API,
# so we can't test equality.
for all_tasks in shot_id_to_task.values():
for task_type, tasks in all_tasks.items():
all_tasks[task_type] = {task['_id'] for task in tasks}
self.assertEqual({
'fx': {task1['_id'], task2['_id']},
'høken': {task3['_id']},
}, shot_id_to_task[shot1_id])
self.assertEqual({
'effects': {task4['_id'], task5['_id']},
None: {task6['_id']},
}, shot_id_to_task[shot2_id])
@responses.activate
def test_edit_shot(self):
shot = self.create_shot()
pre_edit_shot = shot.to_dict()
with self.app.test_request_context():
# Log in as project admin user
pillar.auth.login_user(ctd.EXAMPLE_PROJECT_OWNER_ID)
self.mock_blenderid_validate_happy()
# No Etag checking, see T49555
# self.assertRaises(sdk_exceptions.PreconditionFailed,
# self.smngr.edit_shot,
# shot_id=shot['_id'],
# name=u'ผัดไทย',
# description=u'Shoot the Pad Thai',
# status='todo',
# _etag='jemoeder')
self.smngr.edit_shot(shot_id=shot['_id'],
name='ผัดไทย',
description='Shoot the Pad Thai',
status='todo',
notes=None,
_etag=shot._etag)
# Test directly with MongoDB
with self.app.test_request_context():
nodes_coll = self.app.data.driver.db['nodes']
found = nodes_coll.find_one(ObjectId(shot['_id']))
self.assertEqual(pre_edit_shot['name'], found['name']) # shouldn't be edited.
self.assertEqual('todo', found['properties']['status'])
self.assertEqual('Shoot the Pad Thai', found['description'])
self.assertNotIn('notes', found['properties'])
@responses.activate
def test_shot_summary(self):
shot1 = self.create_shot()
shot2 = self.create_shot()
shot3 = self.create_shot()
shot4 = self.create_shot()
with self.app.test_request_context():
# Log in as project admin user
pillar.auth.login_user(ctd.EXAMPLE_PROJECT_OWNER_ID)
self.mock_blenderid_validate_happy()
for shot, status in zip([shot1, shot2, shot3, shot4],
['todo', 'in_progress', 'todo', 'final']):
self.smngr.edit_shot(shot_id=shot['_id'],
status=status,
_etag=shot._etag)
# def shot_status_summary(self, project_id):
class PatchShotTest(AbstractShotTest):
@responses.activate
def test_patch_from_blender_happy(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-blender',
'$set': {
'name': '"shot" is "geschoten" in Dutch',
'properties.trim_start_in_frames': 123,
'properties.trim_end_in_frames': 0,
'properties.duration_in_edit_in_frames': 4215,
'properties.cut_in_timeline_in_frames': 1245,
'properties.status': 'on_hold',
}
}
self.patch(url, json=patch, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertEqual('"shot" is "geschoten" in Dutch', dbnode['name'])
self.assertEqual(123, dbnode['properties']['trim_start_in_frames'])
self.assertEqual(0, dbnode['properties']['trim_end_in_frames'])
self.assertEqual('on_hold', dbnode['properties']['status'])
@responses.activate
def test_patch_activity(self):
"""Perform the edit, then check the resulting activity on the shot."""
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
# Only change the name -- the activity should contain both the old and the new name.
old_name = shot['name']
new_name = '"shot" is "geschoten" in Dutch'
patch = {
'op': 'from-blender',
'$set': {
'name': new_name,
}
}
self.patch(url, json=patch, auth_token='token')
with self.app.test_request_context():
acts = self.attract.activities_for_node(shot._id)
self.assertEqual(2, acts['_meta']['total']) # Creation + edit
edit_act = acts['_items'][1]
self.assertIn(old_name, edit_act['verb'])
self.assertIn(new_name, edit_act['verb'])
pass
@responses.activate
def test_patch_from_web_happy(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-web',
'$set': {
'description': 'Таким образом, этот человек заходит в бар, и говорит…',
'properties.notes': 'Два бокала вашей лучшей водки, пожалуйста.',
'properties.status': 'final',
}
}
self.patch(url, json=patch, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertEqual('Таким образом, этот человек заходит в бар, и говорит…',
dbnode['description'])
self.assertEqual('Два бокала вашей лучшей водки, пожалуйста.',
dbnode['properties']['notes'])
self.assertEqual('final', dbnode['properties']['status'])
self.assertEqual('New shot', dbnode['name'])
@responses.activate
def test_patch_from_web_happy_nones(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-web',
'$set': {
'description': None,
'properties.notes': None,
'properties.status': 'final',
}
}
self.patch(url, json=patch, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertNotIn('description', dbnode)
self.assertNotIn('notes', dbnode['properties'])
self.assertEqual('final', dbnode['properties']['status'])
self.assertEqual('New shot', dbnode['name'])
@responses.activate
def test_patch_bad_op(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
patch = {'properties.status': 'todo'}
self.patch(url, json=patch, auth_token='token', expected_status=400)
@responses.activate
def test_patch_from_blender_bad_fields(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-blender',
'$set': {
'invalid.property': 'JE MOEDER',
}
}
self.patch(url, json=patch, auth_token='token', expected_status=400)
@responses.activate
def test_patch_from_blender_bad_status(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-blender',
'$set': {
'properties.status': 'JE MOEDER',
}
}
self.patch(url, json=patch, auth_token='token', expected_status=422)
@responses.activate
def test_patch_unauthenticated(self):
shot = self.create_shot()
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-blender',
'$set': {
'properties.status': 'in_progress',
}
}
self.patch(url, json=patch, expected_status=403)
@responses.activate
def test_patch_bad_user(self):
shot = self.create_shot()
self.create_user(24 * 'a')
self.create_valid_auth_token(24 * 'a', 'other')
url = '/api/nodes/%s' % shot._id
patch = {
'op': 'from-blender',
'$set': {
'properties.status': 'in_progress',
}
}
self.patch(url, json=patch, auth_token='other', expected_status=403)
@responses.activate
def test_patch_unlink(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
dbnode = self.get(url, auth_token='token').json()
self.assertTrue(dbnode['properties']['used_in_edit'])
patch = {'op': 'unlink'}
self.patch(url, json=patch, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertFalse(dbnode['properties']['used_in_edit'])
@responses.activate
def test_patch_unlink_deleted(self):
"""Unlinking a deleted shot shouldn't undelete it.
We implement PATCH by changing then PUTing, which undeletes by default.
"""
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
# Delete (and verify deletion)
self.delete(url, auth_token='token',
headers={'If-Match': shot['_etag']},
expected_status=204)
self.get(url, auth_token='token', expected_status=404)
patch = {'op': 'unlink'}
self.patch(url, json=patch, auth_token='token')
self.get(url, auth_token='token', expected_status=404)
@responses.activate
def test_patch_relink(self):
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
self.patch(url, json={'op': 'unlink'}, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertFalse(dbnode['properties']['used_in_edit'])
self.patch(url, json={'op': 'relink'}, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertTrue(dbnode['properties']['used_in_edit'])
@responses.activate
def test_patch_relink_deleted(self):
"""Relinking a deleted shot should undelete it.
We implement PATCH by changing then PUTing, which undeletes.
"""
shot = self.create_shot()
self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token')
url = '/api/nodes/%s' % shot._id
# Delete (and verify deletion)
self.delete(url, auth_token='token',
headers={'If-Match': shot['_etag']},
expected_status=204)
self.get(url, auth_token='token', expected_status=404)
patch = {'op': 'relink'}
self.patch(url, json=patch, auth_token='token')
dbnode = self.get(url, auth_token='token').json()
self.assertTrue(dbnode['properties']['used_in_edit'])
class RequiredAfterCreationTest(AbstractShotTest):
"""
This tests Pillar stuff, but requires attract_shot since that's what the
required_after_creation=False was created for.
Placing the test here was easier than creating a node type in Pillar
specifically for this test case. Once we use that validator in Pillar
itself, we can move this test there too.
"""
def test_create_shot(self):
from attract.node_types import node_type_shot
self.user_id = self.create_project_admin(self.project)
self.create_valid_auth_token(self.user_id, 'token')
node_type_name = node_type_shot['name']
shot = {'name': 'test shot',
'description': '',
'properties': {'trim_start_in_frames': 0,
'trim_end_in_frames': 0,
'duration_in_edit_in_frames': 1,
'cut_in_timeline_in_frames': 0},
'node_type': node_type_name,
'project': str(self.proj_id),
'user': str(self.user_id)}
resp = self.post('/api/nodes', json=shot,
auth_token='token', expected_status=201)
info = resp.json()
resp = self.get('/api/nodes/%(_id)s' % info, auth_token='token')
json_shot = resp.json()
self.assertEqual(node_type_shot['dyn_schema']['status']['default'],
json_shot['properties']['status'])
return json_shot
# TODO: should test editing a shot as well, but I had issues with the PillarSDK
# not handling deleting of properties.
class ProjectSummaryTest(unittest.TestCase):
def setUp(self):
from attract.shots_and_assets import ProjectSummary
self.summ = ProjectSummary()
self.summ.count('todo')
self.summ.count('todo')
self.summ.count('in-progress')
self.summ.count('überhard')
self.summ.count('Æon Flux')
self.summ.count('Æon Flux')
self.summ.count('in-progress')
self.summ.count('todo')
def test_counting(self):
self.assertEqual(8, self.summ._total)
self.assertEqual(3, self.summ._counts['todo'])
self.assertEqual(2, self.summ._counts['Æon Flux'])
def test_percentages(self):
percs = list(self.summ.percentages())
self.assertEqual(('in-progress', 25), percs[0])
self.assertEqual(('todo', 38), percs[1])
self.assertEqual(('Æon Flux', 25), percs[2])
# This should be rounded down, not rounded up, to ensure the sum of
# percentages is 100.
self.assertEqual(('überhard', 12), percs[3])

Event Timeline