132 lines
5.9 KiB
Python
132 lines
5.9 KiB
Python
import asyncio
|
|
|
|
from app import repository
|
|
from app.config import get_settings
|
|
from app.services import index_service, workspace_service
|
|
|
|
|
|
def test_open_returns_before_vectors_and_deduplicates_background(monkeypatch):
|
|
async def scenario():
|
|
started, release = asyncio.Event(), asyncio.Event()
|
|
original = index_service.prepare_note_index
|
|
calls = 0
|
|
async def slow(*args, **kwargs):
|
|
nonlocal calls
|
|
calls += 1
|
|
started.set()
|
|
await release.wait()
|
|
return await original(*args, **kwargs)
|
|
monkeypatch.setattr(index_service, 'prepare_note_index', slow)
|
|
vault = get_settings().vault_path
|
|
vault.mkdir(parents=True, exist_ok=True)
|
|
(vault / 'demo.md').write_text('# Demo\n\nsearchable content', encoding='utf-8')
|
|
try:
|
|
snapshot = await asyncio.wait_for(workspace_service.open_workspace(None), 1)
|
|
assert snapshot.items[0].note_id
|
|
await asyncio.wait_for(started.wait(), 1)
|
|
task = index_service._background_task
|
|
await asyncio.wait_for(workspace_service.open_workspace(None), 1)
|
|
assert index_service._background_task is task
|
|
assert index_service.get_status().status == 'running'
|
|
# A mutation still completes while the model is waiting.
|
|
await asyncio.wait_for(workspace_service.create_folder('/', 'new-folder'), 1)
|
|
assert repository.list_note_locations()[0].note_id == snapshot.items[0].note_id
|
|
release.set()
|
|
await asyncio.wait_for(task, 2)
|
|
assert calls == 1
|
|
assert not index_service.get_status().vector_refresh_required
|
|
finally:
|
|
release.set()
|
|
await index_service.shutdown()
|
|
asyncio.run(scenario())
|
|
|
|
|
|
def test_background_retries_changed_snapshot_without_overwriting(monkeypatch):
|
|
async def scenario():
|
|
started, release = asyncio.Event(), asyncio.Event()
|
|
original = index_service.prepare_note_index
|
|
calls = 0
|
|
async def slow(*args, **kwargs):
|
|
nonlocal calls
|
|
calls += 1
|
|
if calls == 1:
|
|
started.set()
|
|
await release.wait()
|
|
return await original(*args, **kwargs)
|
|
monkeypatch.setattr(index_service, 'prepare_note_index', slow)
|
|
vault = get_settings().vault_path
|
|
vault.mkdir(parents=True, exist_ok=True)
|
|
path = vault / 'demo.md'
|
|
path.write_text('# Before\n\nold', encoding='utf-8')
|
|
try:
|
|
await workspace_service.open_workspace(None)
|
|
await asyncio.wait_for(started.wait(), 1)
|
|
path.write_text('# After\n\nnew', encoding='utf-8')
|
|
release.set()
|
|
await asyncio.wait_for(index_service._background_task, 4)
|
|
assert calls == 2
|
|
assert repository.list_note_locations()[0].title == 'After'
|
|
assert not index_service.get_status().vector_refresh_required
|
|
finally:
|
|
release.set()
|
|
await index_service.shutdown()
|
|
asyncio.run(scenario())
|
|
|
|
|
|
def test_save_returns_while_vectors_wait_and_latest_revision_wins(monkeypatch):
|
|
from app.services import note_service
|
|
async def scenario():
|
|
note = await note_service.create_note(title='Draft', markdown='# Draft\n\ninitial', folder=None, tags=[])
|
|
started, release = asyncio.Event(), asyncio.Event()
|
|
original = index_service.prepare_note_index
|
|
calls = 0
|
|
async def slow(*args, **kwargs):
|
|
nonlocal calls
|
|
calls += 1
|
|
if calls == 1:
|
|
started.set()
|
|
await release.wait()
|
|
return await original(*args, **kwargs)
|
|
monkeypatch.setattr(index_service, 'prepare_note_index', slow)
|
|
try:
|
|
await asyncio.wait_for(note_service.update_note(note.note_id, markdown='# First\n\none', defer_vectors=True), 1)
|
|
await asyncio.wait_for(started.wait(), 1)
|
|
await asyncio.wait_for(note_service.update_note(note.note_id, title='Custom title', tags=['kept'], markdown='# Latest\n\ntwo', defer_vectors=True), 1)
|
|
assert (await note_service.get_note(note.note_id)).markdown == '# Latest\n\ntwo'
|
|
assert index_service.get_status().vector_refresh_required
|
|
release.set()
|
|
await asyncio.wait_for(index_service._background_task, 3)
|
|
current = repository.get_note_record(note.note_id)
|
|
assert current.title == 'Custom title'
|
|
assert current.tags == ['kept']
|
|
assert calls == 2
|
|
assert not index_service.get_status().vector_refresh_required
|
|
finally:
|
|
release.set()
|
|
await index_service.shutdown()
|
|
asyncio.run(scenario())
|
|
|
|
|
|
def test_failed_vectors_do_not_undo_save_and_pending_work_can_resume(monkeypatch):
|
|
from app.services import note_service
|
|
async def scenario():
|
|
note = await note_service.create_note(title='Draft', markdown='# Draft', folder=None, tags=[])
|
|
original = index_service.prepare_note_index
|
|
async def fail(*args, **kwargs):
|
|
raise RuntimeError('model unavailable')
|
|
monkeypatch.setattr(index_service, 'prepare_note_index', fail)
|
|
try:
|
|
await note_service.update_note(note.note_id, markdown='# Saved', defer_vectors=True)
|
|
await index_service._background_task
|
|
assert (await note_service.get_note(note.note_id)).markdown == '# Saved'
|
|
assert index_service.get_status().status == 'failed'
|
|
assert index_service.get_status().vector_refresh_required
|
|
await index_service.shutdown()
|
|
monkeypatch.setattr(index_service, 'prepare_note_index', original)
|
|
await workspace_service.open_workspace(None)
|
|
await index_service._background_task
|
|
assert not index_service.get_status().vector_refresh_required
|
|
finally:
|
|
await index_service.shutdown()
|
|
asyncio.run(scenario())
|