"""Opt-in export concurrency tests, sharing the disposable-only database guard.""" import asyncio from concurrent.futures import ThreadPoolExecutor from threading import Barrier import pytest from fastapi import HTTPException from test_workflow_db import db from app.workflows import export_guard as guard @pytest.fixture def draft(db,monkeypatch): with db.cursor() as cursor: cursor.execute('SELECT id FROM customers ORDER BY id LIMIT 1') customer=cursor.fetchone()[0] cursor.execute("INSERT INTO ordre_drafts(title,customer_id) VALUES('Workflow export concurrency test',%s) RETURNING id",(customer,)) draft_id=cursor.fetchone()[0] monkeypatch.setattr(guard,'is_workflow_draft',lambda _:True) yield draft_id,customer with db.cursor() as cursor: cursor.execute('DELETE FROM bmc_workflow_exports WHERE draft_id=%s',(draft_id,)) cursor.execute('DELETE FROM ordre_drafts WHERE id=%s',(draft_id,)) def test_concurrent_export_and_success_replay_make_one_external_call(db,draft): draft_id,customer=draft calls=[] start=Barrier(2) request={'customer_id':customer,'lines':[{'line_key':'test','selected':True}]} async def exporter(**kwargs): calls.append(kwargs) await asyncio.sleep(.1) return {'success':True,'dry_run':False,'economic_draft_id':12345} def send(): start.wait(timeout=10) try: return asyncio.run(guard.guarded_export(draft_id,exporter,request)) except HTTPException as exc: return exc.status_code with ThreadPoolExecutor(max_workers=2) as pool: results=list(pool.map(lambda _:send(),range(2))) assert len(calls)==1 assert 409 in results replay=asyncio.run(guard.guarded_export(draft_id,exporter,request)) assert replay['economic_draft_id']==12345 assert len(calls)==1 with db.cursor() as cursor: cursor.execute('SELECT status FROM bmc_workflow_exports WHERE draft_id=%s',(draft_id,)) assert cursor.fetchone()[0]=='done' def test_timeout_persists_uncertainty_and_blocks_forced_new_export(db,draft): draft_id,customer=draft calls=[] request={'customer_id':customer,'lines':[],'force_export':True} async def exporter(**kwargs): calls.append(kwargs) raise asyncio.TimeoutError() with pytest.raises(asyncio.TimeoutError): asyncio.run(guard.guarded_export(draft_id,exporter,request)) with pytest.raises(HTTPException) as error: asyncio.run(guard.guarded_export(draft_id,exporter,request)) assert error.value.status_code==409 assert len(calls)==1 with db.cursor() as cursor: cursor.execute('SELECT status FROM bmc_workflow_exports WHERE draft_id=%s',(draft_id,)) assert cursor.fetchone()[0]=='uncertain'