Add Android animation library browsing

This commit is contained in:
2026-10-10 14:51:16 +08:00
parent 5db283c05a
commit c9dd03c23b
39 changed files with 1015 additions and 46 deletions
@@ -50,7 +50,7 @@ def test_progressive_preview_and_revision_cleanup(tmp_path):
from PIL import Image
app = create_app(project_root=tmp_path, driver_kind='mock')
mobile = app.state.mobile_control
assert mobile.identity()['protocol_minor'] == 2
assert mobile.identity()['protocol_minor'] == 3
assert 'wifi_transport' in mobile.identity()['capabilities']
assert 'library_progressive' in mobile.identity()['capabilities']
created = mobile.templates.create('预览清理', {
@@ -220,3 +220,46 @@ def test_task_returns_only_sanitized_failure_code(tmp_path, failure):
result = mobile.dispatch('task.get', {'task_id': task})
assert result == {'state': 'failed', 'stage': 'finished', 'error_code': failure}
assert 'private' not in str(result)
def test_animation_chunks_and_read_only(tmp_path):
import base64
import hashlib
from io import BytesIO
from PIL import Image
mobile = create_app(project_root=tmp_path, driver_kind='mock').state.mobile_control
item = next(i for i in mobile.dispatch('library.list', {})['items'] if i['type'] == 'animation')
info = mobile.dispatch('library.animation.info', {**item, 'limit': 1})
assert info['frames'][0]['index'] == 0
frames = list(info['frames'])
while info['next_offset'] is not None:
info = mobile.dispatch('library.animation.info', {**item, 'limit': 1, 'offset': info['next_offset']})
frames += info['frames']
assert len(frames) == info['frame_count']
assert all(f['duration_ms'] > 0 for f in frames)
before = mobile.dispatch('frame.get', {})['frame_revision']
for tier, size in [('preview', 16), ('full', 64)]:
for frame in frames:
offset, data = 0, bytearray()
while True:
result = mobile.dispatch('library.animation.chunk', {**item, 'tier': tier, 'frame_index': frame['index'], 'offset': offset})
chunk = base64.b64decode(result['data_base64'], validate=True)
assert 0 < len(chunk) <= 4096 and result['offset'] == offset
data.extend(chunk)
if result['next_offset'] is None:
break
assert result['next_offset'] == offset + len(chunk)
offset = result['next_offset']
assert len(data) == result['total_bytes']
assert hashlib.sha256(data).hexdigest() == result['sha256']
with Image.open(BytesIO(data)) as image:
assert image.size == (size, size)
assert mobile.dispatch('frame.get', {})['frame_revision'] == before
for bad in [{'offset': -1}, {'limit': 51}, {'offset': True}]:
with pytest.raises(RpcError, match='BAD_REQUEST'):
mobile.dispatch('library.animation.info', {**item, **bad})
for bad in [{'frame_index': -1}, {'frame_index': True}, {'tier': 'invalid'}, {'offset': 999999}]:
with pytest.raises(RpcError, match='BAD_REQUEST'):
mobile.dispatch('library.animation.chunk', {**item, 'tier': 'full', 'frame_index': 0, **bad})
with pytest.raises(RpcError, match='CONFLICT'):
mobile.dispatch('library.animation.info', {**item, 'revision': 'stale'})