"""Protocol robustness tests against a synthetic command, never a real remote host.""" from dataclasses import replace import json import os from pathlib import Path import sys import threading import time import pytest from aim_webgui.core.client import CoreClient from aim_webgui.config import Settings from aim_webgui.core.protocol import validate_secrets, response_result, COUNTS from aim_webgui.errors import WebError @pytest.fixture def transport(tmp_path): script=tmp_path/'fake.py' settings=Settings(core_transport='stdio',core_command=(sys.executable,str(script)),core_config=tmp_path/'core.yml') return script,settings def script_write(script,body): script.write_text('import sys,json,os,time,stat\n'+body) def test_credentials_go_only_to_nonstandard_pipe_fd(transport): script,settings=transport script_write(script,""" line=sys.stdin.buffer.readline(); request=json.loads(line) assert 'credentials' not in request fd=int(sys.argv[sys.argv.index('--credentials-fd')+1]);assert fd>=3 and stat.S_ISFIFO(os.fstat(fd).st_mode) with os.fdopen(fd,'rb')as stream:secret=json.load(stream) assert secret['vault_password']=='fixture+%!{{data}}' assert secret['vault_password'].encode() not in line assert all(secret['vault_password']not in x for x in os.environ.values()) assert all(secret['vault_password']not in x for x in sys.argv) print(json.dumps({'type':'response','api_version':'1.0','ok':False,'result':{ 'status':'failed','stage':'credentials','exit_code':None,'remote_work_may_have_started':False, 'error':{'code':'vault_unlock_failed'},'counts':{}, 'target_summary':{'schema':'target_outcome_summary_v1','requested':1,'successful':0,'failed':0,'unreachable':0,'not_started':1,'indeterminate':0,'complete':True,'accounted':1}, 'targets':[{'host':'h','outcome':'not_started','counts':{'ok':0,'changed':0,'failures':0,'unreachable':0,'skipped':0,'rescued':0,'ignored':0}}]}}),flush=True) """) request={'customer':'a','playbook':'p','hosts':['h']} result=CoreClient(settings).request('execute',request=request,expected_revision='a'*64,credentials={'vault_password':'fixture+%!{{data}}'}) assert result['status']=='failed' and result['error']['code']=='vault_unlock_failed' @pytest.mark.parametrize('body,code',[ ("print('not-json',flush=True)",'core_protocol'), ("print(json.dumps({'type':'event','event':{'event_version':'1.0','kind':'stage','sequence':1,'stage':'execution'}}),flush=True)",'core_outcome_unknown'), ("print(json.dumps({'type':'response','api_version':'9','ok':True,'result':{}}),flush=True)",'core_protocol'), ("print('X'*1048577,flush=True)",'core_protocol'), ]) def test_invalid_wire_is_not_success(transport,body,code): script,settings=transport;script_write(script,body) with pytest.raises(WebError)as e:CoreClient(settings).request('capabilities') assert e.value.code==code def test_cancellation_stops_child(transport): script,settings=transport;script_write(script,'time.sleep(60)') cancel=threading.Event();timer=threading.Timer(.25,cancel.set);timer.start() try: with pytest.raises(WebError)as e:CoreClient(settings).request('capabilities',cancel=cancel) assert e.value.code=='core_cancelled' finally:timer.cancel() def test_start_deadline_cancels_preparation(transport): script,settings=transport;script_write(script,'time.sleep(60)') with pytest.raises(WebError)as e: CoreClient(settings).request('execute',request={'customer':'a','playbook':'p','hosts':['h']}, expected_revision='a'*64,deadline=time.monotonic()+.3) assert e.value.code=='credential_expired' @pytest.mark.parametrize('secret',[{'vault_password':'x\n'},{'username':'admin'},{'vault_password':'\ud800'},{'become_password':'x'}]) def test_invalid_secrets_rejected(secret): with pytest.raises(WebError):validate_secrets(secret) def test_success_needs_valid_complete_counters(): result={'type':'response','api_version':'1.0','ok':True,'result':{'status':'succeeded','exit_code':0,'remote_work_may_have_started':True,'counts':dict.fromkeys(COUNTS,0),'target_summary':{'schema':'target_outcome_summary_v1','requested':0,'successful':0,'failed':0,'unreachable':0,'not_started':0,'indeterminate':0,'complete':True,'accounted':0},'targets':[]}} assert response_result(result,'execute')['status']=='succeeded' result['result']['counts']['ok']=-1 with pytest.raises(WebError):response_result(result,'execute')