Update revision and document tables and add more unit tests.
This commit is contained in:
parent
f230d9b364
commit
8b79789425
|
@ -72,7 +72,7 @@ class BaseResource(object):
|
|||
|
||||
def return_error(self, resp, status_code, message="", retry=False):
|
||||
resp.body = json.dumps(
|
||||
{'type': 'error', 'message': message, 'retry': retry})
|
||||
{'type': 'error', 'message': str(message), 'retry': retry})
|
||||
resp.status = status_code
|
||||
|
||||
|
||||
|
|
|
@ -121,16 +121,15 @@ def document_create(values, session=None):
|
|||
values['schema_version'] = values.pop('schemaVersion')
|
||||
session = session or get_session()
|
||||
|
||||
filters = copy.copy(models.Document.UNIQUE_CONSTRAINTS)
|
||||
filters = [f for f in filters if f != 'revision_index']
|
||||
filters = models.Document.UNIQUE_CONSTRAINTS
|
||||
existing_document = document_get(**{c: values[c] for c in filters})
|
||||
|
||||
created_document = {}
|
||||
|
||||
def _document_changed():
|
||||
other_document = copy.deepcopy(existing_document)
|
||||
other_document = other_document.to_dict()
|
||||
# The document has changed if at least one value in ``values`` differs.
|
||||
for key, val in values.items():
|
||||
if val != other_document[key]:
|
||||
if val != existing_document[key]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -139,62 +138,87 @@ def document_create(values, session=None):
|
|||
with session.begin():
|
||||
document.update(values)
|
||||
document.save(session=session)
|
||||
return document
|
||||
return document.to_dict()
|
||||
|
||||
created_document = {}
|
||||
if existing_document:
|
||||
# Only generate a new revision and entirely new document if anything
|
||||
# was changed.
|
||||
if _document_changed():
|
||||
revision_index = revision_update(
|
||||
revision_index=existing_document['revision_index'])['id']
|
||||
values['revision_index'] = revision_index
|
||||
created_document = _document_create().to_dict()
|
||||
# TODO: indicate that now document was actually created.
|
||||
created_document = _document_create()
|
||||
revision_update(created_document['id'], existing_document['id'])
|
||||
else:
|
||||
revision_index = revision_create()['id']
|
||||
values['revision_index'] = revision_index
|
||||
created_document = _document_create().to_dict()
|
||||
created_document = _document_create()
|
||||
revision_create(created_document['id'])
|
||||
|
||||
return created_document
|
||||
|
||||
|
||||
def document_get(session=None, **filters):
|
||||
session = session or get_session()
|
||||
|
||||
document = session.query(models.Document)\
|
||||
.filter_by(**filters)\
|
||||
.options(sa_orm.joinedload("revision_index"))\
|
||||
.order_by(desc(models.Revision.created_at))\
|
||||
.first()
|
||||
|
||||
document = session.query(models.Document).filter_by(**filters).first()
|
||||
return document.to_dict() if document else {}
|
||||
|
||||
|
||||
####################
|
||||
|
||||
|
||||
def revision_create(session=None):
|
||||
def revision_create(document_id, session=None):
|
||||
session = session or get_session()
|
||||
revision = models.Revision()
|
||||
with session.begin():
|
||||
revision.update({'document_id': document_id})
|
||||
revision.save(session=session)
|
||||
|
||||
return revision.to_dict()
|
||||
|
||||
|
||||
def revision_update(session=None, revision_index=None):
|
||||
def revision_get(document_id, session=None):
|
||||
session = session or get_session()
|
||||
previous_revision = session.query(models.Revision).get(revision_index)
|
||||
revision = session.query(models.Revision)\
|
||||
.filter_by(document_id=document_id).first()
|
||||
return revision.to_dict()
|
||||
|
||||
new_revision = models.Revision()
|
||||
|
||||
def revision_update(document_id, child_document_id, session=None):
|
||||
"""Create a parent revision and update the child revision.
|
||||
|
||||
The ``document_id`` references the newly created document that is a more
|
||||
up-to-date revision. Create a new (parent) revision that references
|
||||
``document_id`` and whose ``child_id`` is ``child_document_id``.
|
||||
|
||||
Set the ``parent_id`` for ``child_revision`` to ``document_id``.
|
||||
|
||||
After this function has executed, the following relationship is true:
|
||||
|
||||
parent_document <-- parent_revision
|
||||
^ /
|
||||
\ (has child)
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
/ (has parent)
|
||||
v \
|
||||
child_document <-- child_revision
|
||||
|
||||
:param document_id: The ID corresponding to the up-to-date document.
|
||||
:param child_document_id: The ID corresponding tothe out-of-date document.
|
||||
:param session: The database session.
|
||||
:returns: The dictionary representation of the newly created revision.
|
||||
"""
|
||||
session = session or get_session()
|
||||
parent_revision = models.Revision()
|
||||
with session.begin():
|
||||
# Create the new revision with a reference to the previous revision.
|
||||
new_revision.update({'previous': revision_index})
|
||||
new_revision.save(session=session)
|
||||
parent_revision.update({'document_id': document_id,
|
||||
'child_id': child_document_id})
|
||||
parent_revision.save(session=session)
|
||||
|
||||
# Update the previous revision with a reference to the new revision.
|
||||
previous_revision.update({'next': new_revision.id})
|
||||
previous_revision.save(session=session)
|
||||
child_revision = session.query(models.Revision)\
|
||||
.filter_by(document_id=child_document_id).first()
|
||||
with session.begin():
|
||||
child_revision.update({'parent_id': document_id})
|
||||
child_revision.save(session=session)
|
||||
|
||||
return new_revision.to_dict()
|
||||
return parent_revision.to_dict()
|
||||
|
|
|
@ -128,20 +128,20 @@ class Revision(BASE, DeckhandBase):
|
|||
|
||||
id = Column(String(36), primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()))
|
||||
previous = Column(Integer, ForeignKey('revisions.id'), nullable=True)
|
||||
next = Column(Integer, ForeignKey('revisions.id'), nullable=True)
|
||||
document_id = Column(Integer, ForeignKey('documents.id'), nullable=True)
|
||||
parent_id = Column(Integer, ForeignKey('documents.id'), nullable=True)
|
||||
child_id = Column(Integer, ForeignKey('documents.id'), nullable=True)
|
||||
document = relationship("Document", back_populates="revision",
|
||||
foreign_keys=[document_id])
|
||||
|
||||
|
||||
class Document(BASE, DeckhandBase):
|
||||
UNIQUE_CONSTRAINTS = ('schema_version', 'kind', 'revision_index')
|
||||
UNIQUE_CONSTRAINTS = ('schema_version', 'kind')
|
||||
__tablename__ = 'documents'
|
||||
__table_args__ = (DeckhandBase.gen_unqiue_contraint(*UNIQUE_CONSTRAINTS),)
|
||||
#__table_args__ = (DeckhandBase.gen_unqiue_contraint(*UNIQUE_CONSTRAINTS),)
|
||||
|
||||
id = Column(String(36), primary_key=True,
|
||||
default=lambda: str(uuid.uuid4()))
|
||||
revision_index = Column(Integer, ForeignKey('revisions.id'),
|
||||
nullable=False)
|
||||
revision = relationship(Revision, backref=backref('revisions'))
|
||||
schema_version = Column(String(64), nullable=False)
|
||||
kind = Column(String(64), nullable=False)
|
||||
# NOTE: Do not define a maximum length for these JSON data below. However,
|
||||
|
@ -149,6 +149,9 @@ class Document(BASE, DeckhandBase):
|
|||
# "metadata" is reserved, so use "doc_metadata" instead.
|
||||
doc_metadata = Column(JSONEncodedDict(), nullable=False)
|
||||
data = Column(JSONEncodedDict(), nullable=False)
|
||||
revision = relationship("Revision", uselist=False,
|
||||
back_populates="document",
|
||||
foreign_keys="[Revision.document_id]")
|
||||
|
||||
|
||||
def register_models(engine):
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
from deckhand.tests.unit import base
|
||||
|
@ -24,7 +26,7 @@ class DocumentFixture(object):
|
|||
|
||||
def get_minimal_fixture(self, **kwargs):
|
||||
fixture = {'data': 'fake document data',
|
||||
'metadata': 'fake meta',
|
||||
'metadata': 'fake metadata',
|
||||
'kind': 'FakeConfigType',
|
||||
'schemaVersion': 'deckhand/v1'}
|
||||
fixture.update(kwargs)
|
||||
|
@ -43,7 +45,60 @@ class TestDocumentsApi(base.DeckhandWithDBTestCase):
|
|||
self.assertIn(key, actual)
|
||||
self.assertEqual(val, actual[key])
|
||||
|
||||
def _validate_revision(self, revision):
|
||||
expected_attrs = ('id', 'document_id', 'child_id', 'parent_id')
|
||||
for attr in expected_attrs:
|
||||
self.assertIn(attr, revision)
|
||||
self.assertThat(revision[attr], matchers.MatchesAny(
|
||||
matchers.Is(None), matchers.IsInstance(unicode)))
|
||||
|
||||
def test_create_document(self):
|
||||
fixture = DocumentFixture().get_minimal_fixture()
|
||||
document = db_api.document_create(fixture)
|
||||
self._validate_document(fixture, document)
|
||||
|
||||
revision = db_api.revision_get(document['id'])
|
||||
self._validate_revision(revision)
|
||||
self.assertEqual(document['id'], revision['document_id'])
|
||||
|
||||
def test_create_and_update_document(self):
|
||||
"""
|
||||
Check that the following relationship is true:
|
||||
|
||||
parent_document <-- parent_revision
|
||||
^ /
|
||||
\ (has child)
|
||||
\ /
|
||||
\ /
|
||||
\ /
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
/ (has parent)
|
||||
v \
|
||||
child_document <-- child_revision
|
||||
"""
|
||||
fixture = DocumentFixture().get_minimal_fixture()
|
||||
child_document = db_api.document_create(fixture)
|
||||
|
||||
fixture['metadata'] = 'modified fake metadata'
|
||||
parent_document = db_api.document_create(fixture)
|
||||
self._validate_document(fixture, parent_document)
|
||||
|
||||
# Validate that the new document was created.
|
||||
self.assertEqual('modified fake metadata',
|
||||
parent_document['doc_metadata'])
|
||||
self.assertNotEqual(child_document['id'], parent_document['id'])
|
||||
|
||||
# Validate that the parent document has a different revision and
|
||||
# that the revisions and document links are correct.
|
||||
child_revision = db_api.revision_get(child_document['id'])
|
||||
parent_revision = db_api.revision_get(parent_document['id'])
|
||||
for revision in (child_revision, parent_revision):
|
||||
self._validate_revision(revision)
|
||||
|
||||
self.assertNotEqual(child_revision['id'], parent_revision['id'])
|
||||
self.assertEqual(parent_document['id'],
|
||||
parent_revision['document_id'])
|
||||
self.assertEqual(child_document['id'], parent_revision['child_id'])
|
||||
self.assertEqual(parent_document['id'], child_revision['parent_id'])
|
||||
|
|
Loading…
Reference in New Issue