Skip to content
This repository was archived by the owner on Feb 20, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion api/tests/test_article.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
from webob.multidict import MultiDict

import managers

from api.views.article import (
ArticleAPI,
ArticleXML,
ArticleAsset,
ArticleManifest,
)
from managers.models.article_model import ArticleDocument
from persistence.databases import DBFailed
from persistence.databases import DBFailed, UpdateFailure


def _get_file_property(filename, content, size):
Expand Down Expand Up @@ -429,6 +430,66 @@ def test_post_article_returns_article_version_url(mocked_post_article,
assert response.json.get('url').endswith(xml_file.filename)


@patch.object(managers, 'delete_article')
def test_http_article_calls_delete_article_service_unavailable(
mocked_delete_article,
dummy_request):
mocked_delete_article.side_effect = DBFailed
dummy_request.DELETE = MultiDict(
[('id', 'ID')]
)
article_api = ArticleAPI(dummy_request)
with pytest.raises(HTTPServiceUnavailable):
article_api.delete()


@patch.object(managers, 'delete_article')
def test_http_article_calls_delete_article_not_found(
mocked_delete_article,
dummy_request):
mocked_delete_article.side_effect = \
managers.article_manager.ArticleManagerException(
message='Article ID not registered'
)
dummy_request.DELETE = MultiDict(
[('id', 'ID')]
)
article_api = ArticleAPI(dummy_request)
with pytest.raises(HTTPNotFound):
article_api.delete()


@patch.object(managers, 'delete_article')
def test_http_article_calls_delete_article_bad_request(
mocked_delete_article,
dummy_request):
error_msg = 'Article ID is not allowed to delete'
mocked_delete_article.side_effect = \
UpdateFailure(
message=error_msg
)
dummy_request.DELETE = MultiDict(
[('id', 'ID')]
)
article_api = ArticleAPI(dummy_request)
with pytest.raises(HTTPBadRequest) as excinfo:
article_api.delete()
assert excinfo.value.message == error_msg


@patch.object(managers, 'delete_article')
def test_http_article_calls_delete_article_success(
mocked_delete_article,
dummy_request):
dummy_request.DELETE = MultiDict(
[('id', 'ID')]
)
article_api = ArticleAPI(dummy_request)
response = article_api.delete()
assert response.status_code == 200
assert response.json is not None


@patch.object(managers, 'get_article_document')
def test_http_get_article_manifest_db_failed(
mocked_get_article_document,
Expand Down Expand Up @@ -483,3 +544,4 @@ def test_http_get_article_manifest_succeeded(
response = article_api.get()
assert response.status == '200 OK'
assert response.json == expected

16 changes: 16 additions & 0 deletions api/views/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from prometheus_client import Summary

import managers
import persistence


REQUEST_TIME_API_ARTICLE_GET = Summary(
Expand Down Expand Up @@ -105,6 +106,21 @@ def get(self):
except managers.article_manager.ArticleManagerException as e:
raise HTTPNotFound(detail=e.message)

def delete(self):
"""Delete Article."""
try:
article_data = managers.delete_article(
article_id=self.request.DELETE['id'],
**self.request.db_settings
)
return Response(status_code=200, json=article_data)
except managers.article_manager.ArticleManagerException as e:
raise HTTPNotFound(detail=e.message)
except persistence.databases.UpdateFailure as e:
raise HTTPBadRequest(detail=e.message)
except persistence.databases.DBFailed as e:
raise HTTPServiceUnavailable()


@resource(path='/articles/{id}/_manifest', renderer='json')
class ArticleManifest:
Expand Down
20 changes: 20 additions & 0 deletions managers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ def put_article(article_id, xml_file, assets_files=[], **db_settings):
files=assets_files)


def delete_article(article_id, **db_settings):
"""
Marca o Documento de Artigo como "apagado"

:param article_id: ID do Documento do tipo Artigo, para identificação
referencial
:param db_settings: dicionário com as configurações do banco de dados.
Deve conter:
- database_uri: URI do banco de dados (host:porta)
- database_username: usuário do banco de dados
- database_password: senha do banco de dados

:returns:
- HTTPServiceUnavailable
"""
article_manager = _get_article_manager(**db_settings)
return article_manager.delete_article(
article_id=article_id)


def get_article_data(article_id, **db_settings):
"""
Recupera metadados do Documento de Artigo, usados para controle de
Expand Down
4 changes: 4 additions & 0 deletions managers/article_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ def __init__(self, articles_db_manager, changes_services):
self.article_db_service = DatabaseService(
articles_db_manager, changes_services)

def delete_article(self, article_id):
document_record = self.article_db_service.read(article_id)
self.article_db_service.delete(article_id, document_record)

def receive_package(self, id, xml_file, files=None):
article = self.receive_xml_file(id, xml_file)
self.receive_asset_files(article, files)
Expand Down
2 changes: 2 additions & 0 deletions managers/models/article_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def _v0_to_v1(self, record):
version['assets'] = assets
versions = [version]
_record['versions'] = versions
if record.get('deleted_date') or record.get('is_removed'):
_record['is_removed'] = 'True'
return _record

def set_data(self, data):
Expand Down
63 changes: 61 additions & 2 deletions managers/tests/test_article_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from persistence.databases import (
DocumentNotFound,
DBFailed,
UpdateFailure,
)
from persistence.services import DatabaseService
from persistence.models import RecordType

from persistence.models import get_record, RecordType
from managers.models.article_model import (
ArticleDocument,
)
Expand Down Expand Up @@ -226,3 +226,62 @@ def test_get_asset_files(databaseservice_params, test_package_A):
assert len(msg) == 0
for asset in files:
assert asset.content in asset_contents


@patch.object(DatabaseService, 'read')
def test_delete_article_db_failed(
mocked_dataservices_read,
setup,
databaseservice_params):
article_id = 'ID'
mocked_dataservices_read.side_effect = DBFailed
article_manager = ArticleManager(
databaseservice_params[0],
databaseservice_params[1]
)
pytest.raises(
DBFailed,
article_manager.delete_article,
article_id)


@patch.object(DatabaseService, 'delete')
def test_delete_article_update_failure(
mocked_dataservices_delete,
setup,
databaseservice_params):
article_id = 'ID'
error_msg = 'Article ID not allowed to delete'
article_manager = ArticleManager(
databaseservice_params[0],
databaseservice_params[1]
)
article_manager.article_db_service.register(
article_id,
get_record(article_id)
)

mocked_dataservices_delete.side_effect = \
UpdateFailure(error_msg)
with pytest.raises(UpdateFailure) as excinfo:
article_manager.delete_article(article_id)
assert excinfo.value.message == error_msg


def test_delete_article_success(
setup,
databaseservice_params):
article_id = 'ID'
article_manager = ArticleManager(
databaseservice_params[0],
databaseservice_params[1]
)
article_record = get_record(article_id)
article_manager.article_db_service.register(
article_id,
article_record
)
assert article_manager.delete_article(article_id) is None

deleted = article_manager.get_article_document(article_id)
assert deleted.manifest.get('is_removed') == 'True'
37 changes: 25 additions & 12 deletions persistence/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class QueryOperator(Enum):
class BaseDBManager(metaclass=abc.ABCMeta):

_attachments_properties_key = 'attachments_properties'
rev_key = 'document_rev'
_rev_key = '_rev'

@abc.abstractmethod
def drop_database(self) -> None:
Expand Down Expand Up @@ -104,6 +106,7 @@ def __init__(self, **kwargs):
self._database_name = kwargs['database_name']
self._attachments_key = 'attachments'
self._attachments_properties_key = 'attachments_properties'
self._rev_key = 'revision'
self._database = {}

@property
Expand All @@ -117,23 +120,23 @@ def drop_database(self):
self._database = {}

def create(self, id, document):
document['revision'] = 1
document[self._rev_key] = 1
self.database.update({id: document})

def read(self, id):
doc = self.database.get(id)
if not doc:
raise DocumentNotFound
doc['document_rev'] = doc['revision']
doc[self.rev_key] = doc[self._rev_key]
return doc

def update(self, id, document):
_document = self.read(id)
if _document.get('revision') != document.get('document_rev'):
if _document.get(self._rev_key) != document.get(self.rev_key):
raise UpdateFailure(
'You are trying to update a record which data is out of date')
'You are trying to update a record which is out of date')
_document.update(document)
_document['revision'] += 1
_document[self._rev_key] += 1
self.database.update({id: _document})

def delete(self, id):
Expand Down Expand Up @@ -274,7 +277,7 @@ def create(self, id, document):
def read(self, id):
try:
doc = dict(self.database[id])
doc['document_rev'] = doc['_rev']
doc[self.rev_key] = doc[self._rev_key]
except couchdb.http.ResourceNotFound:
raise DocumentNotFound
return doc
Expand All @@ -284,14 +287,24 @@ def update(self, id, document):
Para atualizar documento no CouchDB, é necessário informar a
revisão do documento atual. Por isso, é obtido o documento atual
para que os dados dele sejam atualizados com o registro informado.

Retorno. Uma das opções:
- DocumentNotFound
- Falha de atualização
"""
doc = self.read(id)
if doc.get('_rev') != document.get('document_rev'):
raise UpdateFailure(
'You are trying to update a record which data is out of date')
read = self.read(id)

if self._rev_key not in document.keys():
if self.rev_key in document.keys():
document[self._rev_key] = document[self.rev_key]
read.update(document)
document = read

doc.update(document)
self.database[id] = doc
try:
self.database[id] = document
except:
raise UpdateFailure(
'You are trying to update a record which is out of date')

def delete(self, id):
doc = self.read(id)
Expand Down
23 changes: 16 additions & 7 deletions persistence/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from prometheus_client import Summary

from .databases import QueryOperator
from .databases import QueryOperator, UpdateFailure


REQUEST_TIME_CHANGES_UPD = Summary(
Expand Down Expand Up @@ -133,8 +133,9 @@ def read(self, document_id):
'created_date': document['created_date'],
'document_rev': document['document_rev'],
}
if document.get('updated_date'):
document_record['updated_date'] = document['updated_date']
for optional in ['updated_date', 'is_removed']:
if optional in document.keys():
document_record[optional] = document[optional]
attachments = self.db_manager.list_attachments(document_id)
if attachments:
document_record['attachments'] = \
Expand Down Expand Up @@ -169,12 +170,20 @@ def delete(self, document_id, document_record):
document_id: ID do documento a ser deletado
document_record: registro de documento a ser deletado

Erro:
Erros:
DocumentNotFound: documento não encontrado na base de dados.
UpdateFailure: documento não apagado da base de dados.
"""
self.db_manager.delete(document_id)
self.changes_service.register_change(
document_record, ChangeType.DELETE)
document_record.update({
'is_removed': 'True',
})
try:
self.db_manager.update(document_id, document_record)
self.changes_service.register_change(
document_record, ChangeType.DELETE)
except UpdateFailure:
raise UpdateFailure(
'Document {} not allowed to delete'.format(document_id))

@REQUEST_TIME_DOC_FIND.time()
def find(self, selector, fields, sort):
Expand Down
Loading