import pytest import copy from viano_bug_analysis.analysis.investigation_blocks import block_view, restore_blocks from viano_bug_analysis.analysis.investigation_consolidation import consolidated_view from viano_bug_analysis.analysis.investigation_view import working_view from viano_bug_analysis.analysis.investigation_prompt import build_prompt from viano_bug_analysis.analysis.model_context import model_json from test_investigation_consolidation import populated, capture def checked(c): original = copy.deepcopy(c) view = block_view(c) assert model_json(restore_blocks(view)) == model_json(working_view(c)) assert len(model_json(view)) <= len(model_json(consolidated_view(c))) assert c == original assert model_json(block_view(c)) == model_json(view) return view def test_contiguous_code_has_one_start_line_and_plain_text_strings(): c = populated() c['prompt_version'] = 'investigation_project_v7' v = checked(c) source = v['shared_sources']['FILE0001'] assert source['line_defaults'] == {'text_truncated': False} assert source['blocks'] == [{'start_line': 1, 'lines': ['code line '+str(i) for i in range(1, 121)]}] assert 'lines' not in source for entry in v['evidence'].values(): assert entry['content']['items'][0]['result']['items']['encoding'] == 'shared_source_blocks_v1' def test_out_of_order_ranges_do_not_fill_gaps_or_change_result_order(): c = populated() c['evidence']['E000001']['content']['items'] = [capture(range(200, 280))] c['evidence']['E000002']['content']['items'] = [capture(range(1, 81))] v = checked(c) assert [b['start_line'] for b in v['shared_sources']['FILE0001']['blocks']] == [1, 200] def test_flags_missing_values_conflicts_and_unicode_are_lossless(): c = populated() items = c['evidence']['E000002']['content']['items'][0]['result']['items'] items[0]['text_truncated'] = True items[1]['text_truncated'] = 0 # Must not conflate 0 and false. del items[2]['text_truncated'] items[3]['text'] = '\tUnicode ß with embedded\nnewline and \\ escapes' view = checked(c) assert view['shared_sources']['FILE0001']['conflicting_lines'] == [41, 42, 43, 44] def test_separate_versions_and_duplicate_result_records_remain_distinct(): c = populated() c['evidence']['E000002']['content']['items'] = [capture(range(1, 81), sha='c'*64)] checked(c) c['evidence']['E000002']['content']['items'] = [capture([3, 3, 1])] checked(c) def test_v6_prompt_is_unchanged_and_v7_uses_same_output_schema(): c = populated() old = build_prompt(c) assert old['messages'][1]['content'] == model_json({'task':'investigate_hypothesis', 'input':consolidated_view(c), 'output_schema':old['output_schema']}) c['prompt_version'] = 'investigation_project_v7' new = build_prompt(c) assert new['output_schema'] == old['output_schema'] assert new['messages'][1]['content'] == model_json({'task':'investigate_hypothesis', 'input':block_view(c), 'output_schema':new['output_schema']}) @pytest.mark.parametrize('version',['investigation_project_v7','investigation_project_v10','investigation_project_v11','investigation_project_v12','investigation_project_v13']) def test_new_file_aliases_resolve_before_the_normal_tool_validation(monkeypatch,version): from types import SimpleNamespace from viano_bug_analysis.analysis.investigator import Investigator c = populated() c['prompt_version'] = version state = {'case_id':'case','package_id':'package','analysis_revision':1,'prompt_protocol':2, 'active':{'hypothesis_id':'H1','iteration':1,'context_ref':'context.json'}} class Reader: def __init__(self, *args): pass def __enter__(self): return self def __exit__(self, *args): pass monkeypatch.setattr('viano_bug_analysis.analysis.investigator.PackageReader', Reader) fake = SimpleNamespace(store=SimpleNamespace(limits=None), _latest=lambda *a:'package.zip', _read=lambda *a:c) action = {'operation':'project.query','arguments':{'kind':'source.read','path':'FILE0001','start_line':1,'end_line':3}} original = copy.deepcopy(action) assert Investigator._tool_request(fake, state, action)['requests'][0]['arguments']['path'] == 'code.cpp' assert action == original