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
44 changes: 42 additions & 2 deletions api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,57 @@
import pytest
from pyramid import testing

from managers.article_manager import ArticleManager
from persistence.databases import InMemoryDBManager
from persistence.services import ChangesService
from persistence.seqnum_generator import SeqNumGenerator


@pytest.fixture
def dummy_request():
request = testing.DummyRequest()
request.db_settings = {
'host': 'http://localhost',
'port': '12345',
'db_host': 'http://localhost',
'db_port': '12345'
}
return request


@pytest.fixture
def inmemory_article_manager(request):
db_host = 'http://inmemory'
articles_dbmanager = InMemoryDBManager(database_uri=db_host,
database_name='articles')
files_dbmanager = InMemoryDBManager(database_uri=db_host,
database_name='files')
changes_dbmanager = InMemoryDBManager(database_uri=db_host,
database_name='changes')
changes_seq_dbmanager = InMemoryDBManager(database_uri=db_host,
database_name='changes_seqnum')

def fin():
try:
articles_dbmanager.drop_database()
files_dbmanager.drop_database()
changes_dbmanager.drop_database()
changes_seq_dbmanager.drop_database()
except Exception:
pass

request.addfinalizer(fin)
return ArticleManager(
articles_dbmanager,
files_dbmanager,
ChangesService(
changes_dbmanager,
SeqNumGenerator(
changes_seq_dbmanager,
'CHANGE'
)
)
)


@pytest.fixture
def test_xml_file():
return """
Expand Down
13 changes: 7 additions & 6 deletions api/tests/test_article.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,16 +364,14 @@ def test_http_get_asset_file_succeeded(mocked_get_asset_file,
assert response.content_type == expected[0]


@patch.object(managers, 'create_file')
@patch.object(managers, 'post_article')
def test_post_article_invalid_xml(mocked_post_article,
mocked_create_file,
dummy_request,
test_xml_file):
xml_file = MockCGIFieldStorage("test_xml_file.xml",
BytesIO(test_xml_file.encode('utf-8')))
error_msg = 'Invalid XML Content'
mocked_create_file.side_effect = \
mocked_post_article.side_effect = \
managers.exceptions.ManagerFileError(
message=error_msg
)
Expand Down Expand Up @@ -410,10 +408,13 @@ def test_post_article_internal_error(mocked_post_article,
assert excinfo.value.message == error_msg


@patch.object(managers, 'post_article')
def test_post_article_returns_article_version_url(mocked_post_article,
@patch.object(managers, '_get_article_manager')
def test_post_article_returns_article_version_url(mocked__get_article_manager,
inmemory_article_manager,
dummy_request,
test_xml_file):
mocked__get_article_manager.return_value = inmemory_article_manager
inmemory_db_settings = '/rawfile'
xml_file = MockCGIFieldStorage("test_xml_file.xml",
BytesIO(test_xml_file.encode('utf-8')))
dummy_request.POST = {
Expand All @@ -423,10 +424,10 @@ def test_post_article_returns_article_version_url(mocked_post_article,
article_api = ArticleAPI(dummy_request)
response = article_api.collection_post()

mocked_post_article.assert_called_once()
assert response.status_code == 201
assert response.json is not None
assert response.json.get('url').endswith(xml_file.filename)
assert response.json.get('url').startswith(inmemory_db_settings)


@patch.object(managers, 'get_article_document')
Expand Down
16 changes: 10 additions & 6 deletions api/views/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,22 @@ def _get_file_property(self, file_field):
raise HTTPBadRequest(detail=e.message)

def collection_post(self):
"""
Receive new Article document package which must contain a XML file.
"""
try:
xml_file_field = self.request.POST.get('xml_file')
xml_file = self._get_file_property(xml_file_field)
managers.post_article(
xml_id = Path(xml_file_field.filename).name
xml_file = xml_file_field.file.read()
article_url = managers.post_article(
article_id=self.request.POST['article_id'],
xml_id=xml_id,
xml_file=xml_file,
**self.request.db_settings
)
body = {
'url': '/rawfiles/7ca9f9b2687cb/' + xml_file_field.filename
}
return Response(status_code=201, json=body)
return Response(status_code=201, json={'url': article_url})
except managers.exceptions.ManagerFileError as e:
raise HTTPBadRequest(detail=e.message)
except managers.article_manager.ArticleManagerException as e:
raise HTTPInternalServerError(detail=e.message)

Expand Down
39 changes: 32 additions & 7 deletions managers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from managers.article_manager import ArticleManager
from managers.exceptions import ManagerFileError
from managers.models.article_model import ArticleDocument
from managers.models.article_model import ArticleDocument, InvalidXMLContent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InvalidXMLContent é uma exceção. Não deveria estar baixo managers.exceptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Criei managers.exceptions para ter somente as exceções que serão externalizadas, com o intensão de manter uma interface do que seria o módulo de persistência da aplicação WEB. Não sei se faz sentido.

from managers.models.file import File
from persistence.databases import CouchDBManager
from persistence.services import (
Expand Down Expand Up @@ -32,12 +32,14 @@ def _get_changes_services(db_settings):


def _get_article_manager(**db_settings):
database_config = db_settings
articles_database_config = database_config.copy()
articles_database_config = db_settings.copy()
articles_database_config['database_name'] = "articles"
files_database_config = db_settings.copy()
files_database_config['database_name'] = "files"

return ArticleManager(
CouchDBManager(**articles_database_config),
CouchDBManager(**files_database_config),
_get_changes_services(db_settings)
)

Expand All @@ -57,11 +59,34 @@ def create_file(filename, content):
return File(file_name=filename, content=content)


def post_article(xml_file, **db_settings):
""""""
def post_article(article_id, xml_id, xml_file, **db_settings):
"""
Registra novo documento de artigo em banco de dados informado, persistindo
a versão codificada em XML recebida e um manifesto do artigo contendo a
referência para recuperar o arquivo XML.

:param article_id: ID do Documento do tipo Artigo, para identificação
referencial
:param xml_id: identificação do arquivo
:param xml_file: objeto File-like conteúdo do XML
: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: URL pública para recuperar a versão registrada do artigo
codificado em XML
:rtype: str
"""
article_document = ArticleDocument(article_id)
article_manager = _get_article_manager(**db_settings)
article_manager.add_document()
return xml_file.get_version()
try:
article_document.add_version(xml_id, xml_file)
except InvalidXMLContent as e:
raise ManagerFileError(message=e.message)
else:
return article_manager.add_document(article_document)


def put_article(article_id, xml_file, assets_files=[], **db_settings):
Expand Down
14 changes: 12 additions & 2 deletions managers/article_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ class ArticleManagerMissingAssetFileException(Exception):

class ArticleManager:

def __init__(self, articles_db_manager, changes_services):
def __init__(self, articles_db_manager, files_db_manager,
changes_services):
self.article_db_service = DatabaseService(
articles_db_manager, changes_services)
self.file_db_service = DatabaseService(
files_db_manager, changes_services)

def receive_package(self, id, xml_file, files=None):
article = self.receive_xml_file(id, xml_file)
Expand Down Expand Up @@ -75,7 +78,14 @@ def receive_asset_file(self, article, file):
)

def add_document(self, article_document):
pass
added_file_url = self.file_db_service.add_file(
file_id=article_document.xml_file_id,
content=article_document.xml_tree.content
)
article_document.update_version(added_file_url)
article_record = article_document.get_record()
self.article_db_service.register(article_document.id, article_record)
return "/rawfile/" + article_document.xml_file_id

def get_article_data(self, article_id):
try:
Expand Down
48 changes: 47 additions & 1 deletion managers/models/article_model.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# coding=utf-8
from enum import Enum

import os
from ..xml.article_xml_tree import ArticleXMLTree


class InvalidXMLContent(Exception):
message = "Invalid XML Content"


class DocumentType(Enum):
DOCUMENT = 'DOC'
ARTICLE = 'ART'


class AssetDocument:
"""Metadados de um documento do tipo Ativo Digital.
Um Ativo Digital é um arquivo associado a um documento do tipo Artigo
Expand Down Expand Up @@ -46,13 +56,35 @@ class ArticleDocument:
"""
def __init__(self, article_id):
self.id = article_id
self.versions = []
self.assets = {}
self.unexpected_files_list = []
self._xml_file = None

self.xml_name = None
self.xml_content = None

def add_version(self, file_id, xml_content):
"""Adiciona nova versão de artigo codificado em XML em :attr:`versions`
e cria nova referência para atualizar os dados do manifesto do artigo.
Caso o conteúdo do XML for inválido, a exceção
:class:`InvalidXMLContent` é lançada.
"""
self.xml_tree = ArticleXMLTree(xml_content)
if self.xml_tree.xml_error:
raise InvalidXMLContent
self.xml_file_id = '/'.join([self.xml_tree.checksum[:13], file_id])
self.versions.append({
'data': self.xml_file_id,
'assets': []
})

def update_version(self, added_file_url):
"""
Atualiza referência do artigo codificado em XML em :attr:`versions`.
"""
self.versions[-1].update({'data': added_file_url})

@property
def xml_file(self):
"""Acessa ou define o documento Artigo em XML, representado por uma
Expand All @@ -72,7 +104,9 @@ def xml_file(self):
def xml_file(self, xml_file):
self._xml_file = xml_file
if xml_file is not None:
self.xml_tree = ArticleXMLTree(self._xml_file.content)
self.xml_tree = ArticleXMLTree(xml_file.content)
if self.xml_tree.xml_error:
raise InvalidXMLContent
self.assets = {
name: AssetDocument(node)
for name, node in self.xml_tree.asset_nodes.items()
Expand Down Expand Up @@ -121,6 +155,18 @@ def get_record_content(self):
]
return record_content

def get_record(self):
"""Obtém um dicionário que descreve a instância de
:class:`ArticleDocument` da seguinte maneira: chave ``id``, contendo o
ID do artigo e chave ``versions``, contendo uma lista de versões do
artigo, com a URI da respectiva codificação XML e seus assets.
"""
record_content = {}
record_content['document_id'] = self.id
record_content['document_type'] = DocumentType.ARTICLE.value
record_content['versions'] = self.versions
return record_content

@property
def missing_files_list(self):
"""Obtém uma lista com os nomes dos arquivos dos ativos digitais do
Expand Down
Loading