71 lines
6.0 KiB
Python
71 lines
6.0 KiB
Python
"""Additional live QA evidence: independent appeal, public editorial gates and external dependencies."""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
spec=importlib.util.spec_from_file_location('opportunity_workflow_http_qa',Path(__file__).with_name('opportunity-workflow-http-qa.py'))
|
|
module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
|
|
globals().update({name:getattr(module,name) for name in ['Session','actors','PRIVATE','EXPECTED','REASON','events','page','transition','save','png','review','superuser','promoter','validator','publisher']})
|
|
import json
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
previous = json.loads((PRIVATE / 'opportunity-workflow-http-qa.json').read_text())
|
|
assert previous['result']=='passed' and previous['target']==EXPECTED
|
|
case = next(c for c in previous['cases'] if c['category']=='real_estate')
|
|
land = next(c for c in previous['cases'] if c['category']=='land_rights')
|
|
id=case['id'];document=case['document_id']
|
|
proof={'target':EXPECTED,'active_writes':0,'interface_demonstrated':False,'started_at':datetime.now(timezone.utc).isoformat()}
|
|
path=PRIVATE/'opportunity-workflow-extended-http-qa.json'
|
|
|
|
try:
|
|
assignments=superuser.call('GET','/perfis/gestao',inertia=True)['props']['assignments']
|
|
existing=[g for g in assignments if g['user_id']==actors['bootstrap']['id'] and g['role']=='validator' and g['revoked_at'] is None and 'appeal_reviewer' in json.loads(g['responsibilities'])]
|
|
if not existing:
|
|
superuser.call('POST','/perfis/atribuicoes',{'user_id':actors['bootstrap']['id'],'role':'validator','opportunity_types':['real_estate'],
|
|
'responsibilities':['appeal_reviewer'],'reason':'Instância independente estritamente sintética QA, sem designar responsável real'},status=201)
|
|
appeal=Session('bootstrap').login()
|
|
transition(appeal,id,'appeal_reopen')
|
|
recovered=Session('promoter').login().call('GET',f'/minhas-oportunidades/{id}/view',inertia=True)['props']
|
|
assert recovered['workflowState']=='needs_information' and not recovered['profileContext']['appeal']['pending']
|
|
proof['appeal']={'id':id,'rejector_id':actors['validator']['id'],'appeal_reviewer_id':actors['bootstrap']['id'],'independent_decision':'reopen','recovered_in_new_session':True}
|
|
# No extra copies of originals: new image is a separate, deliberately distinct synthetic fixture.
|
|
image=promoter.call('POST',f'/minhas-oportunidades/{id}/resources/images',{'etag':page(promoter,id)['etag'],'creation_key':str(uuid.uuid4()),'reason':REASON,
|
|
'payload':json.dumps({'title':'Imagem sintética QA','caption':'Ilustração sintética para ensaio QA','alt_text':'Área colorida gerada exclusivamente para QA','is_cover':True,'rights_status':'owned','visibility':'public'})},multipart=png(id+100),status=201)
|
|
resources=promoter.call('GET',f'/minhas-oportunidades/{id}/resources',inertia=True)['props']['resources']
|
|
row=next(i for i in resources['images'] if i['id']==image['id'])
|
|
assert row['file']['malware_scan_status']=='clean'
|
|
promoter.call('POST',f"/minhas-oportunidades/{id}/resources/images/{image['id']}/prepare",{'etag':page(promoter,id)['etag'],'version':row['version'],'reason':REASON})
|
|
resources=promoter.call('GET',f'/minhas-oportunidades/{id}/resources',inertia=True)['props']['resources']
|
|
row=next(i for i in resources['images'] if i['id']==image['id'])
|
|
assert row['payload']['derivatives'] and len(row['derivativePreviews'])==len(row['payload']['derivatives'])
|
|
p=page(promoter,id)['values']['canonical'];p.update({'visibility':'public','images':[image['id']],'review_due_date':'2027-01-01'})
|
|
p['real_estate']['price']=None
|
|
save(promoter,id,p);transition(promoter,id,'submit',status=422)
|
|
p['real_estate']['price']=1000
|
|
save(promoter,id,p);transition(promoter,id,'submit');transition(validator,id,'start_review');review(id,document)
|
|
transition(publisher,id,'publish',status=422,confirm_publication=False)
|
|
transition(publisher,id,'publish',status=409,preview_hash='0'*64)
|
|
transition(publisher,id,'publish')
|
|
published=page(promoter,id)
|
|
public=Session('public').login()
|
|
response=public.call('GET','/api/v1/opportunities/'+published['record']['slug'])
|
|
serialized=json.dumps(response)
|
|
assert 'canonical' not in serialized and 'workflow_context' not in serialized and 'internal_contact_id' not in serialized
|
|
proof['public_publication']={'id':id,'public_route':'/api/v1/opportunities/'+published['record']['slug'],'image_original_clean':True,'derived_previews':len(row['derivativePreviews']),
|
|
'preview_confirmation_enforced':True,'preview_hash_enforced':True,'private_fields_not_exposed':True}
|
|
transition(promoter,id,'request_revision');p['official_title']+=' — proposta privada não publicada';save(promoter,id,p)
|
|
still_public=public.call('GET','/api/v1/opportunities/'+published['record']['slug'])
|
|
assert 'proposta privada não publicada' not in json.dumps(still_public)
|
|
transition(publisher,id,'suspend')
|
|
public.call('GET','/api/v1/opportunities/'+published['record']['slug'],status=404)
|
|
proof['material_change_and_suspend']={'id':id,'public_version_preserved_while_preparing':True,'approval_invalidated':True,'suspended_public_get':404}
|
|
# All internal gates may be recorded, but do not manufacture the missing external authorization.
|
|
transition(validator,land['id'],'start_review');review(land['id'],land['document_id'])
|
|
transition(publisher,land['id'],'publish',status=422)
|
|
proof['external_authorization']={'id':land['id'],'internal_approval':'approved','external_authorization':'pending','publication_status':422,'external_decision_not_fabricated':True}
|
|
proof['result']='passed';proof['completed_at']=datetime.now(timezone.utc).isoformat()
|
|
print(json.dumps({'result':'passed','target':EXPECTED,'appeal_id':id,'independent_actor':actors['bootstrap']['id'],'real_image_derivatives':len(row['derivativePreviews']),'visual_pending':True,'active_writes':0}))
|
|
except Exception:
|
|
proof['result']='failed';raise
|
|
finally:
|
|
proof['events']=events;path.write_text(json.dumps(proof,indent=2),encoding='utf-8')
|