def test_updates_status_pending(self): existing_asset_row = AssetRow( id=None, uploaded_status=UploadedStatus.COMPLETE.value, bucket=DEFAULT_BUCKET, object_key='abc', create_date=datetime.now()) with self.connection.cursor() as cur: existing_asset_row = AssetDao.insert_one(existing_asset_row, cur) with run_server(): response = self.request('put', '/status', data=json.dumps({ 'asset_id': existing_asset_row.id, 'uploaded_status': UploadedStatus.PENDING.value })) self.assertEqual(response.status_code, 200) response_body = json.loads(response.content) with self.connection.cursor() as cur: updated_asset_row = AssetDao.get_by_id(existing_asset_row.id, cur) self.assertEqual(updated_asset_row.uploaded_status, UploadedStatus.PENDING.value) self.assertEqual(response_body, { 'success': True, 'uploaded_status': UploadedStatus.PENDING.value, })
def initiate_upload(upload_request, cursor): """ Creates a signed URL for a put_object operation. Should the URL signing succeed, a row is also written to the `assets` table. Should it not, the exception is exposed to the user and no information is persisted. If the combination of bucket and key already exist in the database, an error is raised and no action is taken. :param upload_request: a dict with keys `object_key` and `expires_in`, denoting the asset's name and the amount of time, in seconds, that the signed URL should last. """ _check_valid_upload_request(upload_request) object_key = upload_request['object_key'] expiration = upload_request.get('expires_in') asset = AssetDao.get_by_bucket_and_key( DEFAULT_BUCKET, object_key, cursor, ) if not asset: new_asset = AssetRow( id=None, uploaded_status=UploadedStatus.PENDING.value, bucket=DEFAULT_BUCKET, object_key=object_key, create_date=datetime.utcnow(), ) asset = AssetDao.insert_one(new_asset, cursor) elif asset.uploaded_status == UploadedStatus.COMPLETE.value: raise UploadInvalidArgsException('Upload already complete, try another object_key.') if expiration: return { 'signed_info': S3Service.create_signed_url( S3ClientMethod.POST_OBJECT, upload_request['object_key'], expiration=expiration, ), 'asset_id': asset.id, } return { 'signed_info': S3Service.create_signed_url( S3ClientMethod.POST_OBJECT, upload_request['object_key'], ), 'asset_id': asset.id, }
def test_happy_path(self): """ Given: An asset_id and uploaded_status are supplied Then: A SQL update is performed """ AssetDao.update_uploaded_status(1, UploadedStatus.COMPLETE.value, self.mock_cursor) self.mock_cursor.execute.assert_called_once_with( 'update asset set uploaded_status = %s where id = %s', (UploadedStatus.COMPLETE.value, 1) )
def test_upload_using_existing_pending_asset(self): pending_asset_row = AssetRow( id=None, uploaded_status=UploadedStatus.PENDING.value, bucket=DEFAULT_BUCKET, object_key='abc', create_date=datetime.now(), ) with self.connection.cursor() as cur: pending_asset_row = AssetDao.insert_one(pending_asset_row, cur) with run_server(): response = self.request( 'post', '/upload', data=json.dumps({ 'object_key': 'abc', 'expires_in': 50 }), ) dict_data = json.loads(response.content) self.assertEqual(dict_data, { 'signed_info': self.post_object_url_response, 'asset_id': pending_asset_row.id, }) self.assertEqual(pending_asset_row.uploaded_status, UploadedStatus.PENDING.value)
def test_upload_existing_completed_asset_fails(self): completed_asset_row = AssetRow( id=None, uploaded_status=UploadedStatus.COMPLETE.value, bucket=DEFAULT_BUCKET, object_key='abc', create_date=datetime.now(), ) with self.connection.cursor() as cur: AssetDao.insert_one(completed_asset_row, cur) with run_server(): response = self.request( 'post', '/upload', data=json.dumps({ 'object_key': 'abc' }), ) self.assertEqual(response.status_code, 400) self.assertTrue('Upload already complete, try another object_key.' in response.content.decode())
def change_asset_upload_status(request, cursor): """ Updates a requested asset to have a new status. :param request: a dict with keys `asset_id` and `uploaded_status`, denoting the asset's id and the new upload status. """ _check_valid_change_upload_status_request(request) asset = AssetDao.get_by_id(request['asset_id'], cursor) if not asset: raise AssetNotFoundException( f'Asset with id {request["asset_id"]} not found') AssetDao.update_uploaded_status(request['asset_id'], request['uploaded_status'], cursor) return { 'success': True, 'uploaded_status': request['uploaded_status'], }
def test_happy_path(self): """ Given: The asset row with the given `bucket_id` and `object_key` exists in the database Then: A dataclass with its row's data is returned """ self.mock_cursor.fetchone.return_value = self.sample_asset_row_tuple result = AssetDao.get_by_bucket_and_key('my_bucket', 'my_object_key', self.mock_cursor) self.mock_cursor.execute.assert_called_once_with( 'select id,uploaded_status,bucket,object_key,create_date ' 'from asset where bucket = %s and object_key = %s', ('my_bucket', 'my_object_key') ) self.assertEqual(result, self.sample_asset_row_dataclass)
def test_not_found(self): """ Given: The request asset_id does not exist in the database Then: `None` is returned """ self.mock_cursor.fetchone.return_value = None result = AssetDao.get_by_id(1, self.mock_cursor) self.mock_cursor.execute.assert_called_once_with( 'select id,uploaded_status,bucket,object_key,create_date ' 'from asset where id = %s', (1,) ) self.assertIsNone(result)
def test_happy_path(self): """ Given: The `asset_id` exists in the database Then: A dataclass with its row's data is returned """ self.mock_cursor.fetchone.return_value = self.sample_asset_row_tuple result = AssetDao.get_by_id(1, self.mock_cursor) self.mock_cursor.execute.assert_called_once_with( 'select id,uploaded_status,bucket,object_key,create_date ' 'from asset where id = %s', (1,) ) self.assertEqual(result, self.sample_asset_row_dataclass)
def test_not_found(self): """ Given: The asset row with the given `bucket_id` and `object_key` does not exist in the database Then: `None` is returned """ self.mock_cursor.fetchone.return_value = None result = AssetDao.get_by_bucket_and_key('my_bucket', 'my_object_key', self.mock_cursor) self.mock_cursor.execute.assert_called_once_with( 'select id,uploaded_status,bucket,object_key,create_date ' 'from asset where bucket = %s and object_key = %s', ('my_bucket', 'my_object_key') ) self.assertEqual(result, None)
def test_upload_writes_asset_properly(self): with run_server(): response = self.request( 'post', '/upload', data=json.dumps({ 'object_key': 'abc' }), ) dict_data = json.loads(response.content) with self.connection.cursor() as cur: asset_row = AssetDao.get_by_bucket_and_key(DEFAULT_BUCKET, 'abc', cur) self.assertEqual(dict_data, { 'signed_info': self.post_object_url_response, 'asset_id': asset_row.id, }) self.assertEqual(asset_row.uploaded_status, UploadedStatus.PENDING.value)
def test_happy_path(self): """ Given: An AssetRow dataclass is supplied to insert Then: The data is inserted, and a dataclass with its row's data is returned """ self.mock_cursor.fetchone.return_value = self.sample_asset_row_tuple result = AssetDao.insert_one(self.pre_insert_dataclass, self.mock_cursor) self.mock_cursor.execute.assert_called_once_with( 'insert into asset(uploaded_status,bucket,object_key,create_date) values(' '%s,%s,%s,%s) returning id,uploaded_status,bucket,object_key,create_date', ( self.pre_insert_dataclass.uploaded_status, self.pre_insert_dataclass.bucket, self.pre_insert_dataclass.object_key, self.pre_insert_dataclass.create_date, ) ) self.assertEqual(result, self.sample_asset_row_dataclass)
def initiate_access(access_request, cursor): """ Creates a signed URL for a get_object operation. The signed URL can only be created for the asset if its uploaded_status is `completed`. :param access_request: a dict with keys `asset_id` and `expires_in`, denoting the asset's name and the amount of time, in seconds, that the signed URL should last. """ _check_valid_access_request(access_request) asset_id = int(access_request['asset_id']) expiration = access_request.get('expires_in') asset = AssetDao.get_by_id(asset_id, cursor) if not asset: raise AssetNotFoundException(f'Asset with id {asset_id} not found') if asset.uploaded_status != UploadedStatus.COMPLETE.value: raise AccessInvalidArgsException('Asset upload is not yet completed.') if expiration: return { 'url': S3Service.create_signed_url( S3ClientMethod.GET_OBJECT, asset.object_key, bucket_name=asset.bucket, expiration=int(expiration), ), 'asset_id': asset.id, } return { 'url': S3Service.create_signed_url( S3ClientMethod.GET_OBJECT, asset.object_key, bucket_name=asset.bucket, ), 'asset_id': asset.id, }