guides export/import bundles
This commit is contained in:
146
tests/test_plugins/test_guides_bundle.py
Normal file
146
tests/test_plugins/test_guides_bundle.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Carrying a guide from one site to another.
|
||||
|
||||
Sites are separate databases, and the ones that matter are air-gapped from
|
||||
wherever the guide was written. These tests are about what has to survive that
|
||||
trip and what must not: the blocks and their images travel, the ids do not.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from plugins.guides.services import bundle as bundleservice
|
||||
|
||||
|
||||
PNG = base64.b64decode(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk'
|
||||
'YPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==')
|
||||
|
||||
|
||||
class FakeImage:
|
||||
def __init__(self, imageid, data=PNG, mimetype='image/png',
|
||||
filename='shot.png'):
|
||||
self.guideimageid = imageid
|
||||
self.data = data
|
||||
self.mimetype = mimetype
|
||||
self.filename = filename
|
||||
|
||||
|
||||
class FakeGuide:
|
||||
def __init__(self, blocks, slug='a-guide', title='A Guide',
|
||||
summary='What it does', tags='shopfloor, printers'):
|
||||
self.slug = slug
|
||||
self.title = title
|
||||
self.summary = summary
|
||||
self.tags = tags
|
||||
self.blocks = blocks
|
||||
|
||||
|
||||
def test_a_bundle_carries_what_the_far_side_needs():
|
||||
guide = FakeGuide([{'id': 'b001', 'type': 'paragraph', 'text': 'Hello'}])
|
||||
payload = bundleservice.build(guide, [])
|
||||
assert payload['format'] == 'shopdb-guide'
|
||||
assert payload['slug'] == 'a-guide'
|
||||
assert payload['title'] == 'A Guide'
|
||||
assert payload['summary'] == 'What it does'
|
||||
assert payload['tags'] == 'shopfloor, printers'
|
||||
assert payload['blocks'][0]['text'] == 'Hello'
|
||||
|
||||
|
||||
def test_ids_do_not_travel():
|
||||
"""A guide's id and revision belong to the database that issued them."""
|
||||
guide = FakeGuide([{'id': 'b001', 'type': 'paragraph', 'text': 'Hello'}])
|
||||
payload = bundleservice.build(guide, [])
|
||||
assert 'guideid' not in payload
|
||||
assert 'revision' not in payload
|
||||
assert 'publishedat' not in payload
|
||||
|
||||
|
||||
def test_an_image_travels_inside_the_file_not_beside_it():
|
||||
"""One file. A bundle that needs its images copied alongside it is a
|
||||
bundle that will eventually arrive without them."""
|
||||
guide = FakeGuide([{'id': 'b001', 'type': 'image', 'imageid': 42,
|
||||
'alt': 'the screen'}])
|
||||
payload = bundleservice.build(guide, [FakeImage(42)])
|
||||
|
||||
assert len(payload['images']) == 1
|
||||
assert payload['images'][0]['mimetype'] == 'image/png'
|
||||
assert base64.b64decode(payload['images'][0]['data']) == PNG
|
||||
|
||||
block = payload['blocks'][0]
|
||||
assert 'imageid' not in block, 'a local id means nothing on the far side'
|
||||
assert block['imagetoken'] == payload['images'][0]['token']
|
||||
assert block['alt'] == 'the screen'
|
||||
|
||||
|
||||
def test_the_same_image_used_twice_travels_once():
|
||||
guide = FakeGuide([
|
||||
{'id': 'b001', 'type': 'image', 'imageid': 42},
|
||||
{'id': 'b002', 'type': 'image', 'imageid': 42},
|
||||
])
|
||||
payload = bundleservice.build(guide, [FakeImage(42)])
|
||||
assert len(payload['images']) == 1
|
||||
assert payload['blocks'][0]['imagetoken'] == payload['blocks'][1]['imagetoken']
|
||||
|
||||
|
||||
def test_an_image_the_guide_does_not_use_is_left_behind():
|
||||
"""An orphaned paste from a draft is not part of the procedure."""
|
||||
guide = FakeGuide([{'id': 'b001', 'type': 'paragraph', 'text': 'Hi'}])
|
||||
payload = bundleservice.build(guide, [FakeImage(42), FakeImage(43)])
|
||||
assert payload['images'] == []
|
||||
|
||||
|
||||
def test_a_block_pointing_at_a_missing_image_is_dropped():
|
||||
"""It would render as 'Image unavailable' on the far side, which reads as
|
||||
a failed import rather than as missing data."""
|
||||
guide = FakeGuide([
|
||||
{'id': 'b001', 'type': 'paragraph', 'text': 'Before'},
|
||||
{'id': 'b002', 'type': 'image', 'imageid': 99},
|
||||
{'id': 'b003', 'type': 'paragraph', 'text': 'After'},
|
||||
])
|
||||
payload = bundleservice.build(guide, [])
|
||||
assert [b['type'] for b in payload['blocks']] == ['paragraph', 'paragraph']
|
||||
|
||||
|
||||
def test_a_bundle_survives_the_round_trip():
|
||||
guide = FakeGuide([
|
||||
{'id': 'b001', 'type': 'heading', 'text': 'Steps'},
|
||||
{'id': 'b002', 'type': 'image', 'imageid': 7},
|
||||
{'id': 'b003', 'type': 'checklist',
|
||||
'items': [{'id': 'i1', 'text': 'Tick me'}]},
|
||||
])
|
||||
text = bundleservice.dumps(bundleservice.build(guide, [FakeImage(7)]))
|
||||
back = bundleservice.loads(text)
|
||||
|
||||
assert back['slug'] == 'a-guide'
|
||||
assert [b['type'] for b in back['blocks']] == ['heading', 'image', 'checklist']
|
||||
assert back['images'][0]['data'] == PNG, 'decoded back to bytes, not base64'
|
||||
assert back['blocks'][2]['items'][0]['text'] == 'Tick me'
|
||||
|
||||
|
||||
def test_something_that_is_not_a_bundle_is_refused_before_anything_is_written():
|
||||
with pytest.raises(bundleservice.BundleError):
|
||||
bundleservice.loads('{"hello": "world"}')
|
||||
with pytest.raises(bundleservice.BundleError):
|
||||
bundleservice.loads('not json at all')
|
||||
with pytest.raises(bundleservice.BundleError):
|
||||
bundleservice.loads(json.dumps({'format': 'shopdb-guide', 'version': 1}))
|
||||
|
||||
|
||||
def test_a_bundle_from_a_newer_site_is_refused_rather_than_half_read():
|
||||
newer = json.dumps({'format': 'shopdb-guide', 'version': 99,
|
||||
'slug': 'x', 'blocks': []})
|
||||
with pytest.raises(bundleservice.BundleError) as error:
|
||||
bundleservice.loads(newer)
|
||||
assert 'newer than this site can read' in str(error.value)
|
||||
|
||||
|
||||
def test_an_unreadable_image_names_itself():
|
||||
broken = json.dumps({
|
||||
'format': 'shopdb-guide', 'version': 1, 'slug': 'x', 'blocks': [],
|
||||
'images': [{'token': 'img001', 'mimetype': 'image/png',
|
||||
'filename': 'x.png', 'data': 'not base64 !!'}]})
|
||||
with pytest.raises(bundleservice.BundleError) as error:
|
||||
bundleservice.loads(broken)
|
||||
assert 'img001' in str(error.value)
|
||||
Reference in New Issue
Block a user