"""Test the compiler application as a whole."""
from unittest import TestCase, mock
import io
from arxiv.integration.api import status, service
from arxiv.users.helpers import generate_token
from arxiv.users.auth import scopes
from .. import factory, compiler, domain
from ..services import store, filemanager
OS_ENVIRON = {'JWT_SECRET': 'foosecret'}
[docs]class TestCompilerApp(TestCase):
"""The the app API."""
[docs] def setUp(self):
"""Create a test app and client."""
self.app = factory.create_app()
self.client = self.app.test_client()
self.app.config['JWT_SECRET'] = 'foosecret'
self.app.config['S3_BUCKETS'] = [
# ('arxiv', 'arxiv-compiler'),
('submission', 'test-submission-bucket')
]
self.user_id = '123'
with self.app.app_context():
self.token = generate_token(self.user_id, 'foo@user.com',
'foouser',
scope=[scopes.CREATE_COMPILE,
scopes.READ_COMPILE])
[docs] @staticmethod
def raise_does_not_exist(*args, **kwargs):
"""Raise :class:`store.DoesNotExist`."""
raise store.DoesNotExist('Nope')
[docs] @staticmethod
def raise_no_such_task(*args, **kwargs):
"""Raise :class:`compiler.NoSuchTask`."""
raise compiler.NoSuchTask('Nada')
[docs] @mock.patch(f'{compiler.__name__}.do_nothing', mock.MagicMock())
@mock.patch(f'{service.__name__}.requests.Session')
@mock.patch(f'{store.__name__}.boto3.client', mock.MagicMock())
def test_get_status(self, mock_session):
"""GET the ``getServiceStatus`` endpoint."""
mock_session.return_value.get.return_value.status_code = status.OK
response = self.client.get('/status')
self.assertEqual(response.status_code, status.OK)
[docs] def test_get_nonexistant(self):
"""GET a nonexistant endpoint."""
response = self.client.get('/nowhere')
self.assertEqual(response.status_code, status.NOT_FOUND)
self.assertEqual(
response.json,
{'reason': 'The requested URL was not found on the server. If you'
' entered the URL manually please check your spelling'
' and try again.'}
)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
def test_post_bad_request(self):
"""POST the ``requestCompilation`` endpoint without data."""
response = self.client.post('/', headers={'Authorization': self.token})
self.assertEqual(response.status_code, status.BAD_REQUEST)
self.assertEqual(
response.json,
{'reason': 'The browser (or proxy) sent a request that this server'
' could not understand.'}
)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
@mock.patch('compiler.controllers.filemanager.FileManager')
def test_post_request_compile(self, mock_fm, mock_store, mock_compiler):
"""POST the ``requestCompilation`` endpoint with valid data."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
mock_fm.current_session.return_value.owner.return_value = None
mock_store.current_session.return_value.get_status.side_effect \
= self.raise_does_not_exist
mock_compiler.get_task.side_effect = self.raise_no_such_task
response = self.client.post('/', json={
'source_id': '54',
'checksum': 'a1b2c3d4=',
'output_format': 'pdf'
},
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.ACCEPTED)
self.assertEqual(response.headers['Location'],
'http://localhost/54/a1b2c3d4%3D/pdf')
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_post_compilation_product_exists(self, mock_store, mock_compiler):
"""POST ``requestCompilation`` for existant product."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = self.user_id
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
mock_store.current_session.return_value.get_status.return_value \
= comp_status
mock_compiler.get_task.side_effect = self.raise_no_such_task
response = self.client.post('/', json={
'source_id': source_id,
'checksum': checksum,
'output_format': fmt
},
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.SEE_OTHER)
self.assertEqual(response.headers['Location'],
f'http://localhost/{source_id}/a1b2c3d4%3D/{fmt}')
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_product_exists_unauthorized(self, mock_store, mock_compiler):
"""POST ``requestCompilation`` for existant product, wrong owner."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = '84843'
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
mock_store.current_session.return_value.get_status.return_value \
= comp_status
mock_compiler.get_task.side_effect = self.raise_no_such_task
response = self.client.post('/', json={
'source_id': source_id,
'checksum': checksum,
'output_format': fmt
},
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.FORBIDDEN)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
@mock.patch('compiler.controllers.filemanager.FileManager')
def test_post_task_start_failed(self, mock_fm, mock_store, mock_compiler):
"""Could not start compilation."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
mock_compiler.TaskCreationFailed = compiler.TaskCreationFailed
mock_fm.current_session.return_value.owner.return_value = None
mock_store.current_session.return_value.get_status.side_effect \
= self.raise_does_not_exist
mock_compiler.get_task.side_effect = self.raise_no_such_task
def raise_creation_failed(*args, **kwargs):
raise compiler.TaskCreationFailed('Nope')
mock_compiler.start_compilation.side_effect = raise_creation_failed
response = self.client.post('/', json={
'source_id': '54',
'checksum': 'a1b2c3d4=',
'output_format': 'pdf'
},
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code,
status.INTERNAL_SERVER_ERROR)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_status_completed(self, mock_store, mock_compiler):
"""GET the ``getCompilationStatus`` endpoint with valid data."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = self.user_id
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
mock_store.current_session.return_value.get_status.return_value \
= comp_status
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.OK)
self.assertDictEqual(response.json, comp_status.to_dict())
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_status_not_owner(self, mock_store, mock_compiler):
"""Someone other than the owner requests ``getCompilationStatus``."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = '5943'
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
mock_store.current_session.return_value.get_status.return_value \
= comp_status
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.FORBIDDEN,
'Forbidden user gets a 403 Forbidden response.')
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_status_nonexistant(self, mock_store, mock_compiler):
"""GET ``getCompilationStatus`` for nonexistant task."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
mock_store.current_session.return_value.get_status.side_effect \
= self.raise_does_not_exist
mock_compiler.get_task.side_effect = self.raise_no_such_task
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.NOT_FOUND)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_log(self, mock_store, mock_compiler):
"""GET the ``getCompilationLog`` endpoint with valid data."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = self.user_id
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
comp_log = domain.Product(stream=io.BytesIO(b'foologcontent'))
mock_store.current_session.return_value.get_status.return_value \
= comp_status
mock_store.current_session.return_value.retrieve_log.return_value \
= comp_log
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}/log',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.OK)
self.assertEqual(response.data, b'foologcontent',
"Returns the raw log content")
self.assertEqual(response.headers['Content-Type'],
'text/plain; charset=utf-8')
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_log_not_owner(self, mock_store, mock_compiler):
"""GET the ``getCompilationLog`` by someone who is not the owner."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = '98766'
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
comp_log = domain.Product(stream=io.BytesIO(b'foologcontent'))
mock_store.current_session.return_value.get_status.return_value \
= comp_status
mock_store.current_session.return_value.retrieve_log.return_value \
= comp_log
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}/log',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.FORBIDDEN)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_log_nononexistant(self, mock_store, mock_compiler):
"""GET the ``getCompilationLog`` for nonexistant compilation."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
mock_store.current_session.return_value.get_status.side_effect \
= self.raise_does_not_exist
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}/log',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.NOT_FOUND)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_product(self, mock_store, mock_compiler):
"""GET the ``getCompilationProduct`` endpoint with valid data."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = self.user_id
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
comp_product = domain.Product(
stream=io.BytesIO(b'fooproductcontents'),
checksum='productchxm'
)
mock_store.current_session.return_value.get_status.return_value \
= comp_status
mock_store.current_session.return_value.retrieve.return_value \
= comp_product
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}/product',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.OK)
self.assertEqual(response.data, b'fooproductcontents',
"Returns the raw product content")
self.assertEqual(response.headers['Content-Type'], 'application/pdf')
self.assertEqual(response.headers['ETag'], '"productchxm"')
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_product_not_owner(self, mock_store, mock_compiler):
"""GET the ``getCompilationProduct`` by someone not the owner."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
owner = '98766'
comp_status = domain.Task.from_dict({
'status': 'completed',
'reason': None,
'source_id': source_id,
'output_format': fmt,
'checksum': checksum,
'size_bytes': 123456,
'owner': owner,
'task_id': f'{source_id}/{checksum}/{fmt}'
})
mock_store.current_session.return_value.get_status.return_value \
= comp_status
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}/product',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.FORBIDDEN)
[docs] @mock.patch('arxiv.users.auth.middleware.os.environ', OS_ENVIRON)
@mock.patch('compiler.controllers.compiler')
@mock.patch('compiler.controllers.Store')
def test_get_product_nononexistant(self, mock_store, mock_compiler):
"""GET the ``getCompilationProduct`` for nonexistant compilation."""
mock_compiler.NoSuchTask = compiler.NoSuchTask
source_id = '54'
checksum = 'a1b2c3d4='
fmt = 'pdf'
mock_store.current_session.return_value.get_status.side_effect \
= self.raise_does_not_exist
response = self.client.get(
f'/{source_id}/{checksum}/{fmt}/product',
headers={'Authorization': self.token}
)
self.assertEqual(response.status_code, status.NOT_FOUND)