Files
VianoBugAnalysis/tests/test_bootstrap_nested_shape.py

73 lines
3.7 KiB
Python

import copy
import json
from dataclasses import replace
import pytest
from jsonschema import Draft202012Validator
from test_parameter_and_bootstrap_guard import bootstrap
from test_lean_investigation_decision import reply
from test_llm_caching import config,native,scripted,CACHE
from viano_bug_analysis.analysis.investigation_prompt import build_prompt
from viano_bug_analysis.analysis import InvestigationConfig,InvestigationError
from viano_bug_analysis.llm.gemini import GeminiProvider,bootstrap_shape
def nested_request(version=2):
from test_investigation_lean import lean_context
c=lean_context();c.pop('lean_session');c.update(bootstrap_json_framing=True,bootstrap_framing_version=version)
return build_prompt(c)
def nonempty_reply():
r=reply();r['update']['next_step']=None
r['update']['working_brief']['findings']=[{'id':'B0001','direction':'neutral',
'text':'Observed bounded result.','scope':'Examined snapshot.','evidence_refs':['E000001']}]
return r
@pytest.mark.parametrize('bad',['plain finding',['B0001','neutral'],None,42,True])
def test_native_nested_shape_rejects_wrong_finding_type(bad):
r=nonempty_reply();request=nested_request();before=copy.deepcopy(request['output_schema'])
frame=bootstrap_shape(request['output_schema']);Draft202012Validator.check_schema(frame)
assert request['output_schema']==before
assert Draft202012Validator(frame).is_valid(r)
r['update']['working_brief']['findings'][0]=bad
assert not Draft202012Validator(frame).is_valid(r)
@pytest.mark.parametrize('field,value',[('evidence_refs','E000001'),('id',42),('text',[]),('scope',None)])
def test_native_shape_covers_nested_finding_fields(field,value):
r=nonempty_reply();r['update']['working_brief']['findings'][0][field]=value
assert not Draft202012Validator(bootstrap_shape(nested_request()['output_schema'])).is_valid(r)
@pytest.mark.parametrize('version',[2,3])
def test_new_native_contract_sent_with_cache_and_full_schema_retained(monkeypatch,version):
request=nested_request(version);assert request['prompt_version']==f'investigation_bootstrap_v{20+version}'
response=native();response['candidates'][0]['content']['parts'][0]['text']=json.dumps(nonempty_reply())
adapter=GeminiProvider();seen=scripted(adapter,monkeypatch,[CACHE,response,response])
settings=replace(config(),cache_min_prefix_chars=10**9,response_format='json_object')
first=adapter._generate(settings,request,1000000);second=adapter._generate(settings,request,1000000)
assert first.document==second.document==nonempty_reply()
assert len(seen)==3 and seen[1][1]['cachedContent']==seen[2][1]['cachedContent']
frame=seen[1][1]['generationConfig']['responseJsonSchema']
item=frame['properties']['update']['properties']['working_brief']['properties']['findings']['items']
assert item['type']=='object' and set(item['required'])=={'id','direction','text','scope','evidence_refs'}
assert 'oneOf' not in json.dumps(frame) and len(json.dumps(frame))<5000
# The complete local/cached contract still constrains enum values and IDs.
bad=nonempty_reply();bad['update']['working_brief']['findings'][0]['direction']='invented'
assert not Draft202012Validator(request['output_schema']).is_valid(bad)
assert 'output_schema' in str(seen[0][1]['contents'])
def test_legacy_request_remains_v21_and_schema_identical():
old=bootstrap();new=nested_request()
assert old['prompt_version']=='investigation_bootstrap_v21'
assert new['output_schema']==old['output_schema']
@pytest.mark.parametrize('version',[0,4,True,'2'])
def test_invalid_bootstrap_version_rejected(version):
with pytest.raises(InvestigationError):InvestigationConfig(bootstrap_framing_version=version)