Pre-publication review draft
日常訳NICHIJOYAKU日本語 ↗

OPEN SOURCE

software/tests/test_alignment.py

Read on this site and download the files you need.

Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.

← Back to the open-source guide

software/tests/test_alignment.py146 linesDownload this file ↓

This is the same source included in the download. Each line number is a link you can save or share.

File verification detailsSHA-256 6884c601204aadba3999c187f68fb8c3e13b5c55a45ccef3b17896fae6fe8f81
1import json2import pytest3from src.output import alignment as a456def review(text,targets,decision='mapped',**extra):7    return dict(text_sha256=a.digest(text),mapped_targets=targets,decision=decision,basis='fixture',reviewer='fixture',**extra)8910def test_lossless_routes():11    assert a.route_record('b:1:1','abc',None)==[('b:1:1','abc')]12    assert a.route_record('b:1:1','abc',review('abc',[],'same'))==[('b:1:1','abc')]13    assert a.route_record('b:1:1','abc',review('abc',['b:1:2']))==[('b:1:2','abc')]14    assert a.route_record('b:1:1','abc',review('abc',['b:1:2','b:1:3']))==[]15    assert a.route_record('b:1:1','abc',review('abc',[],'mixed'))==[]16    for record,message in [(review('abd',[]),'text changed'),(review('abc',[],'guess'),'Unknown review')]:17        with pytest.raises(ValueError,match=message):a.route_record('b:1:1','abc',record)18    routes=[dict(target='b:1:1',start=0,end=1),dict(target='b:1:2',start=1,end=3)]19    assert a.route_record('b:1:1','abc',review('abc',[],routes=routes))==[('b:1:1','a'),('b:1:2','bc')]20    with pytest.raises(ValueError,match='Unassigned'):a.route_record('b:1:1','abc',review('abc',[],routes=routes[:1]))21    with pytest.raises(ValueError,match='Character loss'):a.route_record('b:1:1','abc',review('abc',[],routes=routes[1:]))222324def test_conflicts_missing_and_held_extras():25    records={'b:1:1':'first','b:1:2':'other','b:1:3':'unknown'}26    r={'records':{'b:1:2':review('other',['b:1:1']),'b:1:3':review('unknown',[],'unclear')}}27    result=a.align(records,{'b:1:1','b:1:2'},r)28    assert not result['numbering_ready'] and result['missing_passages']==['b:1:2']29    assert len(result['conflicts']['b:1:1'])==230    assert result['extra_references']['b:1:3']['status']=='held'31    r['records']['b:1:3']=review('unknown',['b:1:1'])32    assert a.align(records,{'b:1:1','b:1:2'},r)['extra_references']['b:1:3']['status']=='conflict'333435def test_selections_joins_headings_and_identical_text():36    records={'b:1:1':'title','b:1:2':'same','b:1:3':'same','b:1:4':'end','b:1:5':'wrong'}37    r={'records':{k:review(v,[target]) for (k,v),target in zip(records.items(),['b:1:0','b:1:1','b:1:1','b:1:2','b:1:2'])},'resolutions':{'b:1:2':dict(expected_candidates=['b:1:4','b:1:5'],sources=['b:1:4'],reason='Accurate closing')}}38    result=a.align(records,{'b:1:1','b:1:2'},r)39    assert result['numbering_ready']40    assert result['aligned_changes']['b:1:0']['text']=='title'41    assert result['aligned_changes']['b:1:1']['text']=='same'42    assert result['aligned_changes']['b:1:2']['text']=='end'43    assert len(result['resolutions']['b:1:2']['all_candidates'])==244    assert result['extra_references']['b:1:5']['destinations']['b:1:2']['selected']['sources']==['b:1:4']45    r['resolutions']['b:1:2']['sources']=['b:1:4','b:1:5']46    assert a.align(records,{'b:1:1','b:1:2'},r)['aligned_changes']['b:1:2']['text']=='endwrong'474849def test_invalid_review_rejected():50    with pytest.raises(ValueError,match='unknown source'):a.align({},set(),{'records':{'b:1:1':{}}})51    with pytest.raises(ValueError,match='outside site'):a.align({'b:1:1':'a'},set(),{'records':{}})52    with pytest.raises(ValueError,match='no candidates'):a.align({},set(),{'records':{},'resolutions':{'b:1:1':{}}})53    r={'records':{},'resolutions':{'b:1:1':dict(expected_candidates=['b:1:2'],sources=['b:1:2'],reason='fixture')}}54    with pytest.raises(ValueError,match='candidates changed'):a.align({'b:1:1':'a'},{'b:1:1'},r)55    r['resolutions']['b:1:1']['expected_candidates']=['b:1:1']56    with pytest.raises(ValueError,match='Invalid selection'):a.align({'b:1:1':'a'},{'b:1:1'},r)575859def test_snapshot_and_input_protection(tmp_path,monkeypatch):60    source=tmp_path/'source';chapter=source/'b/chapter_01.json';chapter.parent.mkdir(parents=True)61    chapter.write_text(json.dumps({'chapter':1,'verses':{'1':{'text':'a'}}}))62    records,files=a.read_output(source);assert records=={'b:1:1':'a'}63    rp=tmp_path/'review.json';out=tmp_path/'result.json'64    monkeypatch.setattr(a,'load_site',lambda:({('b',1,1):'reference'},{'b':'b'}))65    r=dict(records={},input_files=files,site_sha256=a.digest(json.dumps({'b:1:1':'reference'},ensure_ascii=False,sort_keys=True)),usage={})66    rp.write_text(json.dumps(r));assert a.build_audit(source,rp,out)['numbering_ready']67    assert json.loads(out.read_text())['summary']['resolved_site_references']==168    for dest in [source/'audit.json',rp]:69        with pytest.raises(ValueError,match='overwrite'):a.build_audit(source,rp,dest)70    r['site_sha256']='changed';rp.write_text(json.dumps(r))71    with pytest.raises(ValueError,match='reference text changed'):a.build_audit(source,rp,out)72    chapter.write_text(json.dumps({'chapter':1,'verses':{'1':{'text':'b'}}}))73    with pytest.raises(ValueError,match='snapshot changed'):a.build_audit(source,rp,out)74    chapter.write_text(json.dumps({'chapter':1,'verses':{'1':{'text':''}}}))75    with pytest.raises(ValueError,match='empty translation'):a.read_output(source)76    chapter.unlink()77    with pytest.raises(ValueError,match='No translations'):a.read_output(source)787980def repair(text, candidates):81    return dict(text=text, expected_candidates=a.candidate_hashes(candidates),82                reason='Reviewed against mapped source', evidence_sha256='a'*64, verified_by='independent review')838485def test_reviewed_repairs_fill_gaps_and_replace_incomplete_candidates():86    records={'b:1:1':'partial'}87    r={'records':{},'target_repairs':{'b:1:1':repair('complete',[{'source':'b:1:1','text':'partial'}]),'b:1:2':repair('restored',[])}}88    result=a.align(records,{'b:1:1','b:1:2'},r)89    assert result['numbering_ready'] and result['summary']['resolved_site_references']==290    assert result['aligned_changes']['b:1:2']['restored']91    assert result['aligned_changes']['b:1:1']['text']=='complete'92    assert result['resolutions']['b:1:1']['all_candidates']==[{'source':'b:1:1','text':'partial'}]93    r['target_repairs']['b:1:3']=repair('outside',[])94    with pytest.raises(ValueError,match='Repair outside'):a.align(records,{'b:1:1','b:1:2'},r)959697def test_repair_requires_unchanged_candidates_clean_text_and_review_evidence():98    item=repair('fixed',[])99    with pytest.raises(ValueError,match='candidates changed'):a.validate_repair('b:1:1',item,[{'source':'b:1:1','text':'different'}])100    for text in ['', ' ', 'bad\ntext', '<span>', '```', None]:101        with pytest.raises(ValueError,match='Invalid repaired text'):a.validate_repair('b:1:1',{**item,'text':text},[])102    for field in ['reason','evidence_sha256','verified_by']:103        with pytest.raises(ValueError,match='lacks review evidence'):a.validate_repair('b:1:1',{**item,field:''},[])104105106def export_fixture(tmp_path,monkeypatch):107    source=tmp_path/'data/output';chapter=source/'b/chapter_01.json';chapter.parent.mkdir(parents=True)108    chapter.write_text(json.dumps({'chapter':1,'verses':{'1':{'text':'original'},'2':{'text':'unchanged'}}}))109    monkeypatch.setattr(a,'load_site',lambda:({('b',1,1):'first',('b',1,2):'second'},{'b':'bb'}))110    _,files=a.read_output(source)111    rp=tmp_path/'review.json';audit=tmp_path/'audit.json'112    r=dict(records={},input_files=files,site_sha256=a.digest(json.dumps({'b:1:1':'first','b:1:2':'second'},ensure_ascii=False,sort_keys=True)),usage={})113    rp.write_text(json.dumps(r));a.build_audit(source,rp,audit)114    return source,rp,audit,r115116117def test_repair_source_files_are_bound_to_audit(tmp_path,monkeypatch):118    source,rp,audit,r=export_fixture(tmp_path,monkeypatch)119    text=source.parent/'sources/hebrew/b.json';text.parent.mkdir(parents=True);text.write_text('source')120    r['repair_source_files']={'sources/hebrew/b.json':a.digest('source')};rp.write_text(json.dumps(r))121    assert a.build_audit(source,rp,audit)['numbering_ready']122    text.write_text('changed')123    with pytest.raises(ValueError,match='Repair source text changed'):a.build_audit(source,rp,audit)124    r['repair_source_files']={'../other':'hash'};rp.write_text(json.dumps(r))125    with pytest.raises(ValueError,match='path outside'):a.build_audit(source,rp,audit)126127128def test_export_complete_release_and_heading(tmp_path,monkeypatch):129    source,rp,audit,r=export_fixture(tmp_path,monkeypatch)130    result=json.loads(audit.read_text());result['aligned_changes']={'b:1:1':{'text':'fixed'},'b:1:0':{'text':'heading'}};audit.write_text(json.dumps(result))131    out=tmp_path/'release';manifest=a.export_aligned(source,audit,out)132    assert manifest['verses']==2 and manifest['chapters']==1 and manifest['books']==1133    book=json.loads((out/'bb.json').read_text())134    assert book['translation']=='日常訳第1版'135    assert book['chapters']['1']=={'heading':'heading','verses':{'1':'fixed','2':'unchanged'}}136    assert manifest['files']['bb.json']==a.digest((out/'bb.json').read_text())137    for dest in [source/'release',source.parent]:138        with pytest.raises(ValueError,match='separate'):a.export_aligned(source,audit,dest)139    with pytest.raises(ValueError,match='empty'):a.export_aligned(source,audit,out)140    result['numbering_ready']=False;audit.write_text(json.dumps(result))141    with pytest.raises(ValueError,match='incomplete'):a.export_aligned(source,audit,tmp_path/'bad')142    result['numbering_ready']=True;result['site_sha256']='changed';audit.write_text(json.dumps(result))143    with pytest.raises(ValueError,match='reference text changed'):a.export_aligned(source,audit,tmp_path/'bad')144    result['site_sha256']=r['site_sha256'];result['aligned_changes']['b:1:1']['text']='';audit.write_text(json.dumps(result))145    with pytest.raises(ValueError,match='Missing release verse'):a.export_aligned(source,audit,tmp_path/'bad')146