Files
MTUCI-RAG-Example/tests/test_upload_retry.py
T

100 lines
3.4 KiB
Python

import asyncio
import io
from types import SimpleNamespace
import rio
import components
from n8n_client import WebhookError
def view():
return SimpleNamespace(collection_name='my-store', pending_files=(), upload_error='',
upload_status='', upload_progress='', is_uploading=False,
force_refresh=lambda: None)
def pick(state, name, data=b'# Notes'):
file = rio.FileInfo(name, len(data), 'text/markdown', io.BytesIO(data))
components.KnowledgeApp.pick_file(state, rio.FilePickEvent(file))
return file
def test_partial_failure_retries_only_failed_file_and_retains_bytes(monkeypatch):
state = view()
pick(state, 'first.md')
pick(state, 'second.md')
pick(state, 'third.md')
calls = []
async def upload(name, contents, collection):
calls.append(name)
assert contents == b'# Notes'
assert collection == 'my-store'
if name == 'second.md' and calls.count(name) == 1:
raise WebhookError('Временная ошибка')
monkeypatch.setattr(components, 'client', SimpleNamespace(upload=upload))
asyncio.run(components.KnowledgeApp.upload_file(state))
assert calls == ['first.md', 'second.md', 'third.md']
assert len(state.pending_files) == 1
assert state.pending_files[0].contents == b'# Notes'
assert 'second.md' in state.upload_error
assert '2 из 3' in state.upload_status
asyncio.run(components.KnowledgeApp.upload_file(state))
assert calls == ['first.md', 'second.md', 'third.md', 'second.md']
assert not state.pending_files
assert not state.upload_error
assert not state.is_uploading
def test_invalid_files_do_not_remove_valid_selection():
state = view()
good = pick(state, 'good.md')
pick(state, 'bad.txt')
pick(state, 'empty.md', b'')
pick(state, 'another.md')
assert len(state.pending_files) == 2
assert 'bad.txt' in state.upload_error and 'empty.md' in state.upload_error
components.KnowledgeApp.remove_file(state, rio.FilePickEvent(good))
assert len(state.pending_files) == 1
assert state.pending_files[0].file.name == 'another.md'
def test_busy_queue_is_immutable_and_duplicate_submission_is_ignored(monkeypatch):
state = view()
first = pick(state, 'first.md')
calls = []
async def upload(*args):
calls.append(args)
pick(state, 'late.md')
components.KnowledgeApp.remove_file(state, rio.FilePickEvent(first))
assert len(state.pending_files) == 1
await components.KnowledgeApp.upload_file(state)
monkeypatch.setattr(components, 'client', SimpleNamespace(upload=upload))
asyncio.run(components.KnowledgeApp.upload_file(state))
assert len(calls) == 1
assert not state.pending_files
def test_read_error_does_not_block_remaining_files(monkeypatch):
state = view()
broken = pick(state, 'broken.md')
state.pending_files[0].file = SimpleNamespace(name='broken.md', read_bytes=broken_read)
pick(state, 'good.md')
calls = []
async def upload(name, *_):
calls.append(name)
monkeypatch.setattr(components, 'client', SimpleNamespace(upload=upload))
asyncio.run(components.KnowledgeApp.upload_file(state))
assert calls == ['good.md']
assert not state.pending_files
assert 'Выберите его заново' in state.upload_error
async def broken_read():
raise OSError('closed stream')