Files

81 lines
4.9 KiB
Python

"""Real GPU/API smoke test, run on CMP. Uses and revokes a disposable device."""
import hashlib
import io
import json
import os
from pathlib import Path
import time
import zipfile
import httpx
from device_auth import DeviceStore
root = Path(os.environ.get('AUDIOBOOK_SERVICE_ROOT', '/home/sevenhill/apps/aletheia-audiobook'))
base = 'https://argus.kusoft.xyz/aletheia-tts'
with httpx.Client(timeout=60) as client:
for attempt in range(30):
try:
health = client.get(base + '/health')
if health.status_code == 200:
break
except httpx.TransportError:
pass
time.sleep(2)
else:
raise RuntimeError('Public receiver health endpoint is unavailable')
store = DeviceStore(root / 'audiobook-jobs/jobs.db3')
response = client.post(base + '/v1/devices/enroll', json={'code': store.create_code(), 'label': 'Deployment smoke test'})
response.raise_for_status()
device = response.json()
headers = {'Authorization': 'Bearer ' + device['token']}
job = None
try:
text = 'Это проверка изготовления аудиокниги. Приёмник получил книгу от приложения и читает её вслух. Готовая запись возвращается в библиотеку.'
book_format = os.environ.get('AUDIOBOOK_SMOKE_FORMAT', 'epub')
if book_format == 'epub':
archive = io.BytesIO()
with zipfile.ZipFile(archive, 'w') as epub:
epub.writestr('mimetype', 'application/epub+zip')
epub.writestr('META-INF/container.xml', '<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0"><rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles></container>')
chapters = int(os.environ.get('AUDIOBOOK_SMOKE_CHAPTERS', '1'))
manifest = ''.join(f'<item id="c{i}" href="chapter{i}.xhtml" media-type="application/xhtml+xml"/>' for i in range(chapters))
spine = ''.join(f'<itemref idref="c{i}"/>' for i in range(chapters))
epub.writestr('OEBPS/content.opf', '<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="bookid"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier id="bookid">cmp-smoke</dc:identifier><dc:title>Проверка CMP</dc:title><dc:language>ru</dc:language></metadata><manifest>' + manifest + '</manifest><spine>' + spine + '</spine></package>')
for i in range(chapters):
body = text if chapters == 1 else text + f' Раздел номер {i + 1}.'
epub.writestr(f'OEBPS/chapter{i}.xhtml', '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Проверка</title></head><body><h1>Проверка ' + str(i + 1) + '</h1><p>' + body + '</p></body></html>')
book = archive.getvalue()
else:
book = ('<?xml version="1.0" encoding="UTF-8"?><FictionBook><body><section><title><p>Проверка</p></title><p>' + text + '</p></section></body></FictionBook>').encode()
response = client.post(base + '/v1/audiobooks', headers=headers, data={'title': 'Проверка CMP', 'author': 'Aletheia'}, files={'book': ('test.' + book_format, book, 'application/epub+zip' if book_format == 'epub' else 'application/xml')})
response.raise_for_status()
job = response.json()['id']
path = base + '/v1/audiobooks/' + job
assert client.get(path).status_code == 401
deadline = time.monotonic() + 1200
previous = None
while time.monotonic() < deadline:
response = client.get(path, headers=headers)
response.raise_for_status()
status = response.json()
state = (status['status'], status['stage'], status['processedCharacters'], status['totalCharacters'])
if state != previous:
print(json.dumps({'state': state}, ensure_ascii=False), flush=True)
previous = state
if status['status'] == 'ready':
response = client.get(path + '/file', headers=headers)
response.raise_for_status()
assert b'ftyp' in response.content[:32] and status['durationMs'] > 0
output = root / 'smoke-audiobook.m4a'
output.write_bytes(response.content)
print(json.dumps({'result': 'PASS', 'bytes': len(response.content), 'durationMs': status['durationMs'], 'sha256': hashlib.sha256(response.content).hexdigest()}), flush=True)
break
if status['status'] in ('failed', 'cancelled'):
raise RuntimeError(status)
time.sleep(3)
else:
raise TimeoutError('Synthesis exceeded smoke test timeout')
finally:
if job:
client.delete(base + '/v1/audiobooks/' + job, headers=headers)
store.revoke(device['deviceId'])