import pytest from src.output.alignment import digest from src.output.repair_review import merge_repairs def fixtures(): review={'records':{'b:1:2':{'decision':'mixed','mapped_targets':['b:1:1']}}} packets=[{'target':'b:1:1','candidates':[{'source':'b:1:2','text':'partial'}],'source_spans':[], 'held_sources':['b:1:2']}] decisions=[{'target':'b:1:1','text':'complete','selected_sources':[],'reason':'Restore missing clause','boundary_issue':''}] checks=[{'target':'b:1:1','status':'pass','text_sha256':digest('complete'),'reviewer':'editor'}] return review,packets,decisions,checks def test_import_preserves_raw_ambiguity_and_requires_exact_verified_text(): r,p,d,c=fixtures();result=merge_repairs(r,p,d,c,'evidence') assert r['records']['b:1:2']['decision']=='mixed' assert result['records']['b:1:2']['decision']=='mapped' assert result['records']['b:1:2']['pre_repair_review']['decision']=='mixed' assert result['target_repairs']['b:1:1']['text']=='complete' d[0]['text']='different' with pytest.raises(ValueError,match='passing verification'):merge_repairs(r,p,d,c,'evidence') def test_selection_and_boundary_review(): r,p,d,c=fixtures();d[0].update(text='partial',selected_sources=['b:1:2'],boundary_issue='boundary question');c[0]['text_sha256']=digest('partial') with pytest.raises(ValueError,match='Unresolved source boundary'):merge_repairs(r,p,d,c,'evidence') c[0]['boundary_resolution']='Checked against mapped word spans' assert merge_repairs(r,p,d,c,'evidence')['target_repairs']['b:1:1']['selected_sources']==['b:1:2'] for chosen in [['b:1:3'],['b:1:2','b:1:2']]: d[0]['selected_sources']=chosen with pytest.raises(ValueError,match='does not match'):merge_repairs(r,p,d,c,'evidence') def test_reject_partial_duplicate_or_changed_inputs(): r,p,d,c=fixtures() with pytest.raises(ValueError,match='Duplicate review'):merge_repairs(r,p+p,d,c,'evidence') with pytest.raises(ValueError,match='Incomplete'):merge_repairs(r,p,d,[],'evidence') r['records']['b:1:2']['mapped_targets']=['b:1:3'] with pytest.raises(ValueError,match='Held record mapping changed'):merge_repairs(r,p,d,c,'evidence') def test_evidence_bundle_is_bound_to_original_review(tmp_path): import json import hashlib from src.output.repair_review import import_bundle r,p,d,c=fixtures();r['usage']={'earlier':'round'} rp=tmp_path/'before.json';rp.write_text(json.dumps(r));bp=tmp_path/'bundle.json';out=tmp_path/'after.json' bundle={'input_review_sha256':hashlib.sha256(rp.read_bytes()).hexdigest(),'packets':p,'decisions':d,'verification':c,'source_files':{},'usage':{'transport':'claude -p'}} bp.write_text(json.dumps(bundle)) assert import_bundle(rp,bp,out)==1 result=json.loads(out.read_text());assert result['usage']['repair_round']['transport']=='claude -p' assert result['target_repairs']['b:1:1']['evidence_sha256']==hashlib.sha256(bp.read_bytes()).hexdigest() for target in [rp,bp]: with pytest.raises(ValueError,match='overwrite its evidence'):import_bundle(rp,bp,target) rp.write_text('{}') with pytest.raises(ValueError,match='different input review'):import_bundle(rp,bp,out)