sahil-12kumar/IL_CMS_Tools
1
1"""push_tags.fetch_many — the thread-parallel QID fetch every prompt builder2(AI Tagging / LO / Verify / Solutions prep) now runs on.3 4The network is mocked at pt.fetch so these tests exercise the executor wiring5(input-order output under out-of-order completion, per-QID error isolation, and6progress reported from the collector thread) without touching the CMS.7"""8import sys9import threading10import time11from pathlib import Path12 13import pytest14 15sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'backend' / 'ai_tagger'))16 17 18@pytest.fixture(scope='module')19def pt():20 import push_tags21 return push_tags22 23 24def test_fetch_many_returns_results_in_input_order(pt, monkeypatch):25 """Workers finish out of order (ILQ-1 is fastest); output must still be the26 input order so prompts built from it are stable."""27 def fake_fetch(h, qid):28 time.sleep(0.02 * int(qid[-1]))29 return {'question_id': qid, 'question': {'question_text': f'q{qid}'}}30 monkeypatch.setattr(pt, 'fetch', fake_fetch)31 out = pt.fetch_many({'h': 1}, ['ILQ-3', 'ILQ-1', 'ILQ-2'], workers=3)32 assert [o['qid'] for o in out] == ['ILQ-3', 'ILQ-1', 'ILQ-2']33 assert all('data' in o and 'error' not in o for o in out)34 35 36def test_fetch_many_isolates_per_qid_errors(pt, monkeypatch):37 def fake_fetch(h, qid):38 if qid == 'ILQ-BAD':39 raise RuntimeError('boom')40 return {'question_id': qid}41 monkeypatch.setattr(pt, 'fetch', fake_fetch)42 out = pt.fetch_many({'h': 1}, ['ILQ-1', 'ILQ-BAD', 'ILQ-2'], workers=3)43 assert [o['qid'] for o in out] == ['ILQ-1', 'ILQ-BAD', 'ILQ-2']44 assert out[1] == {'qid': 'ILQ-BAD', 'error': 'boom'}45 assert 'data' in out[0] and 'data' in out[2]46 47 48def test_fetch_many_reports_progress_from_the_caller_thread(pt, monkeypatch):49 """progress(done, total) fires on the collector (caller) thread only — the50 app's progress handlers write to a job dict under TAG_LOCK, so they must not51 be invoked from worker threads."""52 monkeypatch.setattr(pt, 'fetch', lambda h, qid: {'question_id': qid})53 seen, who = [], set()54 55 def prog(done, total):56 seen.append(done)57 who.add(threading.current_thread().name)58 59 pt.fetch_many({'h': 1}, ['ILQ-1', 'ILQ-2', 'ILQ-3'], workers=3, progress=prog)60 assert seen == [1, 2, 3]61 assert who == {'MainThread'}62 63 64def test_fetch_many_single_worker_and_empty(pt, monkeypatch):65 monkeypatch.setattr(pt, 'fetch', lambda h, qid: {'question_id': qid})66 assert pt.fetch_many({'h': 1}, ['ILQ-1', 'ILQ-2'], workers=1) == [67 {'qid': 'ILQ-1', 'data': {'question_id': 'ILQ-1'}},68 {'qid': 'ILQ-2', 'data': {'question_id': 'ILQ-2'}},69 ]70 assert pt.fetch_many({'h': 1}, []) == []71 