Files
aiturk-hermes-ide/apps/desktop/assets/codex-bridge/test_bekci_watch.py
T
yilsem 2718b460ed
Server CI and Windows package / verify (push) Successful in 3m5s
Server CI and Windows package / windows-package (push) Successful in 13m2s
fix(voice): keep watcher alive through quiet checks and reconnects
2026-10-12 20:06:46 +03:00

358 lines
21 KiB
Python

import concurrent.futures
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
from pathlib import Path
import subprocess
import sys
import threading
import time
import pytest
from bekci_watch import Frames, Ledger, MetadataFilter, WatchOwner, opaque
from native_speech_mux import NativeSpeechMux, stdio_invocation
from test_native_speech_mux import Harness
def wait(predicate):
until=time.time()+5
while time.time()<until:
if predicate(): return
time.sleep(.01)
assert predicate()
class Server:
def __init__(self, frames, status=200):
self.requests=[];self.frames=frames;self.status=status;self.closed=threading.Event()
owner=self
class H(BaseHTTPRequestHandler):
def log_message(self,*args): pass
def do_POST(self):
owner.requests.append((self.path,self.headers.get('Authorization'),json.loads(self.rfile.read(int(self.headers['Content-Length'])))))
self.send_response(owner.status);self.send_header('Content-Type','text/event-stream');self.end_headers()
try:
for frame in owner.frames:
self.wfile.write(frame);self.wfile.flush()
while not owner.closed.wait(.05):
self.wfile.write(b': keepalive\n\n');self.wfile.flush()
except OSError: pass
self.server=ThreadingHTTPServer(('127.0.0.1',0),H);self.server.daemon_threads=True
self.origin='http://127.0.0.1:'+str(self.server.server_port)
threading.Thread(target=self.server.serve_forever,daemon=True).start()
def close(self): self.closed.set();self.server.shutdown();self.server.server_close()
def frame(event,value,cursor=None):
return ((f'id: {cursor}\n' if cursor else '')+f'event: {event}\ndata: '+json.dumps(value)+'\n\n').encode()
def started(mux,tid):
mux._observe_server({'method':'thread/realtime/started','params':{'threadId':tid,'realtimeSessionId':'rt'}})
def meta(): return dict(subscription='s'*40,events_url='/v1/bekci/events',expires_at=(time.time()+60)*1000)
def capture(owner,tid): return owner.capture({'session-id':tid,'thread-id':tid},{'prompt_cache_key':tid})
def notice(i='one'): return dict(notice=dict(id=i,type='arastirma',metin='correction',at=time.time()),cursor='cursor-'+i)
def test_stream_metadata_isolation_split_crlf_and_json():
seen=[];f=MetadataFilter(seen.append);wire=frame('response.output_text.delta',{'delta':'answer'})+frame('bekci.watch',meta())+b'data: [DONE]\n\n'
wire=wire.replace(b'\n',b'\r\n');out=b''
for byte in wire: out+=f.feed(bytes([byte]))
out+=f.flush()
assert len(seen)==1 and b'subscription' not in out and b'answer' in out and b'[DONE]' in out
def test_ready_auth_durable_replay_and_epoch_abort(tmp_path):
n=notice();server=Server([frame('ready',{'cursor':'start'}),frame('notice',n,n['cursor']),frame('notice',n,n['cursor'])])
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
ledger=Ledger(tmp_path/'ledger.db');owner=WatchOwner(mux,ledger,origin=server.origin,test_loopback=True)
c=capture(owner,'A');p=meta()
try:
assert owner.accept(p,c,'member',server.origin+'/v1/responses')=='subscribing'
wait(lambda:mux._queue.qsize()==1)
scope=opaque('public-watch','member',p['subscription'])
wait(lambda:ledger.cursor(scope)==n['cursor'])
assert server.requests[0]==('/v1/bekci/events','Bearer member',{'subscription':p['subscription']})
reopened=Ledger(tmp_path/'ledger.db');assert not reopened.claim(scope,'one')
mux._observe_client({'method':'thread/realtime/stop','params':{'threadId':'A'}})
wait(lambda:not owner.watches)
assert owner.accept(p,c,'member',server.origin+'/v1/responses')=='stale_generation'
finally: owner.close();server.close()
def test_no_ready_wrong_key_untrusted_url_and_two_threads(tmp_path):
n=notice();server=Server([frame('notice',n,n['cursor'])]);mux=NativeSpeechMux(['unused'],observer=True)
for t in ('A','B'): started(mux,t)
owner=WatchOwner(mux,Ledger(tmp_path/'db'),origin=server.origin,test_loopback=True)
try:
c=capture(owner,'A')
assert owner.accept(meta(),c,'member','https://evil.invalid/responses')=='untrusted_route'
assert owner.accept(meta(),None,'member',server.origin)=='stale_generation'
p=meta();p['events_url']='https://evil.invalid';assert owner.accept(p,c,'member',server.origin)=='invalid_receipt'
owner.accept(meta(),c,'member',server.origin);wait(lambda:len(server.requests)>0)
time.sleep(.1);assert mux._queue.empty() and not mux._bindings
finally:owner.close();server.close()
server=Server([frame('ready',{'cursor':'start'}),frame('notice',n,n['cursor'])]);owner=WatchOwner(mux,Ledger(tmp_path/'db2'),origin=server.origin,test_loopback=True)
try:
for t in ('A','B'):
p=meta();p['subscription']=t*40;owner.accept(p,capture(owner,t),'member',server.origin)
wait(lambda:mux._queue.qsize()==2)
assert {mux._queue.get()[1][0]['thread_id'] for _ in range(2)}=={'A','B'}
finally:owner.close();server.close()
def test_launcher_stdio_grammar(tmp_path):
assert stdio_invocation(['-c','x=true','app-server','--analytics-default-enabled','-c','y="z"'])
assert not stdio_invocation(['exec','app-server'])
assert not stdio_invocation(['app-server','--listen','ws://127.0.0.1'])
assert not stdio_invocation(['app-server','daemon','start'])
@pytest.mark.parametrize('active_owner', [True, False])
def test_http_receipt_through_real_bridge_handler_to_real_pipe(tmp_path, active_owner):
"""Real imported H, real HTTP primary/events, real subprocess append/approval."""
import os
import http.client
import urllib.request
bridge=tmp_path/'bridge';bridge.mkdir()
(bridge/'bridge.json').write_text(json.dumps({'aiturk_hat':{'enabled':False},'sesle_model':False,'nokta_karsila':False,'yarim_cevap':False,'ozet_kopru':{'enabled':False},'tasma_bekcisi':False}),encoding='utf-8')
agent=tmp_path/'agent';agent.mkdir()
old=dict(os.environ);os.environ['AITURK_CODEX_BRIDGE_HOME']=str(bridge);os.environ['HERMES_HOME']=str(agent)
try:
import kopru
finally: os.environ.clear();os.environ.update(old)
h=Harness(tmp_path);h.start()
p=meta();n=notice();requests=[]
class Primary(BaseHTTPRequestHandler):
def log_message(self,*args): pass
def do_POST(self):
body=json.loads(self.rfile.read(int(self.headers['Content-Length'])))
requests.append((self.path,self.headers.get('Authorization'),body))
if self.path=='/v1/bekci/events':
wire=frame('ready',{'cursor':'start'})+frame('notice',n,n['cursor'])
kind='text/event-stream'
elif body.get('stream',True):
wire=frame('response.output_text.delta',{'type':'response.output_text.delta','delta':'answer'})+frame('bekci.watch',p)+b'data: [DONE]\n\n';kind='text/event-stream'
else:
wire=json.dumps({'output':[],'_bekci_watch':p}).encode();kind='application/json'
self.send_response(200);self.send_header('Content-Type',kind);self.send_header('Content-Length',str(len(wire)));self.end_headers();self.wfile.write(wire)
upstream=ThreadingHTTPServer(('127.0.0.1',0),Primary);upstream.daemon_threads=True
threading.Thread(target=upstream.serve_forever,daemon=True).start();origin='http://127.0.0.1:'+str(upstream.server_port)
owner=WatchOwner(h.mux,Ledger(tmp_path/'http-ledger'),origin=origin,test_loopback=True)
kopru.WATCH_OWNER=owner if active_owner else None;kopru.MODEL_MAP['ide/test-watch']=('test-route','Sesli-Hizli-Zeki',False)
kopru.ROUTES['test-route']={'url':origin+'/v1/responses','key':'public-member','flatten_tools':False}
server=kopru.TS(('127.0.0.1',0),kopru.H);threading.Thread(target=server.serve_forever,daemon=True).start()
try:
for stream in (True,False):
conn=http.client.HTTPConnection('127.0.0.1',server.server_port,timeout=5)
conn.request('POST','/v1/responses',json.dumps({'model':'ide/test-watch','stream':stream,'prompt_cache_key':'thread-A','input':[]}),{'Content-Type':'application/json','session-id':'thread-A','thread-id':'thread-A'})
resp=conn.getresponse();data=resp.read();conn.close()
assert resp.status==200 and b'subscription' not in data and b'_bekci_watch' not in data
if active_owner:
approval=json.loads(h.recv());speech=json.loads(h.recv())
assert approval['method']=='item/commandExecution/requestApproval'
assert speech['method']=='seen-speech' and speech['params']=={'threadId':'thread-A','text':'correction'}
assert any(path=='/v1/bekci/events' and auth=='Bearer public-member' for path,auth,_ in requests)
else:
assert not any(path=='/v1/bekci/events' for path,_,_ in requests)
finally:
owner.close();server.shutdown();server.server_close();upstream.shutdown();upstream.server_close();kopru.WATCH_OWNER=None;h.close()
def test_http_403_expiry_and_crash_claim_never_resend(tmp_path):
server=Server([],status=403);mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'db'),origin=server.origin,test_loopback=True)
try:
owner.accept(meta(),capture(owner,'A'),'wrong-key',server.origin)
wait(lambda:len(server.requests)==1);wait(lambda:not owner.watches)
assert not mux._bindings and mux._queue.empty()
finally:owner.close();server.close()
n=notice();server=Server([frame('ready',{'cursor':'start'}),frame('notice',n,n['cursor'])])
ledger=Ledger(tmp_path/'crash');p=meta();scope=opaque('public-watch','member',p['subscription']);assert ledger.claim(scope,'one')
owner=WatchOwner(mux,Ledger(tmp_path/'crash'),origin=server.origin,test_loopback=True)
try:
p['expires_at']=(time.time()+.4)*1000
owner.accept(p,capture(owner,'A'),'member',server.origin)
wait(lambda:owner.ledger.cursor(scope)==n['cursor']);wait(lambda:not owner.watches)
assert mux._queue.empty()
finally:owner.close();server.close()
def test_receipt_cannot_move_to_other_thread_or_generation(tmp_path):
server=Server([frame('ready',{'cursor':'start'})]);mux=NativeSpeechMux(['unused'],observer=True)
started(mux,'A');started(mux,'B');owner=WatchOwner(mux,Ledger(tmp_path/'db'),origin=server.origin,test_loopback=True);p=meta()
try:
assert owner.accept(p,capture(owner,'A'),'member',server.origin)=='subscribing'
assert owner.accept(p,capture(owner,'B'),'member',server.origin)=='receipt_replay_mismatch'
mux._observe_client({'method':'thread/unsubscribe','params':{'threadId':'A'}})
wait(lambda:not owner.watches);started(mux,'A')
assert owner.accept(p,capture(owner,'A'),'member',server.origin)=='receipt_replay_mismatch'
finally:owner.close();server.close()
@pytest.mark.parametrize('seconds', [16*60, 60*60, 24*60*60, 24*60*60+30])
def test_protocol_lifetime_receipt_admitted(tmp_path, seconds):
server=Server([frame('ready', {'cursor':'start'})])
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'db'),origin=server.origin,test_loopback=True)
try:
p=meta();p['expires_at']=(time.time()+seconds)*1000
assert owner.accept(p,capture(owner,'A'),'member',server.origin)=='subscribing'
wait(lambda:len(server.requests)==1)
wait(lambda:bool(mux._bindings))
assert owner.watches
finally: owner.close();server.close()
@pytest.mark.parametrize('expiry', [0, -1, float('inf'), float('-inf'), float('nan'), True, False, 'infinite', None, 10**100])
def test_invalid_or_unbounded_expiry_rejected(tmp_path, expiry):
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'db'))
p=meta();p['expires_at']=expiry
assert owner.accept(p,capture(owner,'A'),'member','https://ai.turkservis.online/v1/responses')=='invalid_receipt'
assert not owner.watches
def test_receipt_beyond_protocol_and_skew_rejected(tmp_path):
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'db'))
p=meta();p['expires_at']=(time.time()+24*60*60+120)*1000
assert owner.accept(p,capture(owner,'A'),'member','https://ai.turkservis.online/v1/responses')=='invalid_receipt'
@pytest.mark.parametrize('target', ['Other-Model', 'sesli-hizli-zeki', 'Sesli-Hizli-Zeki-extra'])
@pytest.mark.parametrize('json_response', [False, True])
@pytest.mark.parametrize('jev', [False, True])
def test_actual_handler_nonvoice_preserves_response_pipeline(tmp_path, monkeypatch, target, json_response, jev):
import http.client
import kopru
from unittest.mock import Mock
# Exceed the sideband frame cap: other models must never enter its buffer.
wire=(json.dumps({'output':[], 'payload':'x'*270000}).encode() if json_response
else frame('response.output_text.delta', {'delta':'x'*270000}))
class Primary(BaseHTTPRequestHandler):
def log_message(self,*args): pass
def do_POST(self):
self.rfile.read(int(self.headers['Content-Length']))
self.send_response(200)
self.send_header('Content-Type','application/json' if json_response else 'text/event-stream')
self.send_header('Content-Length',str(len(wire)));self.end_headers();self.wfile.write(wire)
upstream=ThreadingHTTPServer(('127.0.0.1',0),Primary)
threading.Thread(target=upstream.serve_forever,daemon=True).start()
owner=Mock();monkeypatch.setattr(kopru,'WATCH_OWNER',owner)
monkeypatch.setitem(kopru.MODEL_MAP,'ide/scope-proof',('aiturk-ide',target,None))
monkeypatch.setitem(kopru.ROUTES,'aiturk-ide',{'url':f'http://127.0.0.1:{upstream.server_port}/v1/responses','key':'scope-member','flatten_tools':jev})
monkeypatch.setattr(kopru.jev_denetim,'anahtar',lambda:'test-key' if jev else '')
wrapped=Mock(side_effect=AssertionError('nonvoice entered watch wrapper'))
monkeypatch.setattr(kopru,'WatchResponse',wrapped)
shortcut=Mock(side_effect=AssertionError('nonvoice entered JSON shortcut'))
monkeypatch.setattr(kopru.H,'_send_json',shortcut)
calls=[]
original_stream=kopru.H._stream
def stream(self,resp,*args,**kwargs):
calls.append('stream')
return original_stream(self,resp,*args,**kwargs)
def denetim(self,resp,tag,rewriter,izle,istek,sira,ac,*,hedef):
# Verify original upstream object/body reaches Jev, not a watch wrapper.
calls.append('jev');assert hedef==target and sira
data=resp.read();resp.close();assert data==wire
self.send_response(200);self.send_header('Content-Length',str(len(data)))
self.end_headers();self.wfile.write(data)
monkeypatch.setattr(kopru.H,'_stream',stream)
monkeypatch.setattr(kopru.H,'_denetimli_akis',denetim)
server=kopru.TS(('127.0.0.1',0),kopru.H)
threading.Thread(target=server.serve_forever,daemon=True).start()
try:
conn=http.client.HTTPConnection('127.0.0.1',server.server_port,timeout=5)
conn.request('POST','/v1/responses',json.dumps({'model':'ide/scope-proof','stream':not json_response,'input':[]}),{'Content-Type':'application/json'})
response=conn.getresponse();data=response.read();conn.close()
assert response.status==200 and data==wire
assert calls==(['jev'] if jev else ['stream'])
wrapped.assert_not_called();shortcut.assert_not_called();owner.accept.assert_not_called()
finally:
server.shutdown();server.server_close();upstream.shutdown();upstream.server_close()
class QuietWatchServer:
"""Real sockets, with no heartbeat; observes client shutdown at the peer."""
def __init__(self, protocol='HTTP/1.0', delay=0, headers_delay=False, reconnects=False, delayed_headers=False):
self.requests=[];self.closed=threading.Event();self.peer_closed=threading.Event()
self.ready=threading.Event();owner=self
class H(BaseHTTPRequestHandler):
protocol_version='HTTP/1.1' if protocol=='HTTP/1.1-close' else protocol
def log_message(self,*args): pass
def do_POST(self):
body=json.loads(self.rfile.read(int(self.headers['Content-Length'])))
owner.requests.append(body);number=len(owner.requests)
try:
if headers_delay:
owner.ready.set()
self.connection.settimeout(5)
if self.connection.recv(1)==b'': owner.peer_closed.set()
return
if delayed_headers and owner.closed.wait(delay): return
self.send_response(200);self.send_header('Content-Type','text/event-stream')
if protocol=='HTTP/1.1-close': self.send_header('Connection','close')
self.end_headers()
self.wfile.write(frame('ready',{'cursor':'start'}));self.wfile.flush();owner.ready.set()
if reconnects:
n=notice('first' if number<9 else 'ninth')
self.wfile.write(frame('notice',n,n['cursor']));self.wfile.flush()
if number<9:
self.close_connection=True
return
elif delay and number==1:
if not delayed_headers and owner.closed.wait(delay): return
n=notice('delayed');self.wfile.write(frame('notice',n,n['cursor']));self.wfile.flush()
self.connection.settimeout(5 if not delay else 20)
if self.connection.recv(1)==b'': owner.peer_closed.set()
except OSError: pass
self.server=ThreadingHTTPServer(('127.0.0.1',0),H);self.server.daemon_threads=True
self.origin='http://127.0.0.1:'+str(self.server.server_port)
threading.Thread(target=self.server.serve_forever,daemon=True).start()
def close(self):
self.closed.set();self.server.shutdown();self.server.server_close()
@pytest.mark.parametrize('delayed_headers',[False,True])
def test_quiet_watch_delivers_after_more_than_ten_seconds(tmp_path,delayed_headers):
server=QuietWatchServer(delay=11.2,delayed_headers=delayed_headers)
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'quiet'),origin=server.origin,test_loopback=True)
try:
assert owner.accept(meta(),capture(owner,'A'),'member',server.origin)=='subscribing'
assert server.ready.wait(14)
until=time.monotonic()+14
while mux._queue.empty() and time.monotonic()<until: time.sleep(.02)
assert not mux._queue.empty(), 'valid quiet stream lost delayed notice'
assert len(server.requests)==1, 'quiet channel spuriously reconnected'
finally:owner.close();server.close()
@pytest.mark.parametrize('protocol',['HTTP/1.0','HTTP/1.1','HTTP/1.1-close'])
@pytest.mark.parametrize('cancel',['close','expiry','generation'])
@pytest.mark.parametrize('headers_delay',[False,True])
def test_quiet_owned_socket_is_interrupted(tmp_path,protocol,cancel,headers_delay):
server=QuietWatchServer(protocol=protocol,headers_delay=headers_delay)
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'cancel'),origin=server.origin,test_loopback=True)
try:
p=meta()
if cancel=='expiry':p['expires_at']=(time.time()+.7)*1000
owner.accept(p,capture(owner,'A'),'member',server.origin)
assert server.ready.wait(3)
if not headers_delay:wait(lambda:bool(mux._bindings))
if cancel=='close':owner.close()
elif cancel=='generation':mux._observe_client({'method':'thread/realtime/stop','params':{'threadId':'A'}})
assert server.peer_closed.wait(2), 'owned response/header socket remained open'
wait(lambda:not owner.watches)
assert not mux._bindings and mux._queue.empty()
finally:owner.close();server.close()
def test_ninth_reconnect_delivers_and_preserves_cursor_dedup(tmp_path):
class FastBackoff:
def __init__(self):self.event=threading.Event()
def is_set(self):return self.event.is_set()
def set(self):self.event.set()
def wait(self,seconds):return self.event.wait(min(seconds,.01))
server=QuietWatchServer(reconnects=True)
mux=NativeSpeechMux(['unused'],observer=True);started(mux,'A')
owner=WatchOwner(mux,Ledger(tmp_path/'reconnect'),origin=server.origin,test_loopback=True)
p=meta();c=capture(owner,'A');scope=opaque('public-watch','member',p['subscription']);stop=FastBackoff()
owner.watches[scope]=stop
worker=threading.Thread(target=owner._watch,args=(p,c,'member',scope,stop),daemon=True);worker.start()
try:
wait(lambda:len(server.requests)>=9)
wait(lambda:owner.ledger.cursor(scope)=='cursor-ninth')
assert mux._queue.qsize()==2
assert all(req.get('cursor')=='cursor-first' for req in server.requests[1:9])
finally:
owner.close();worker.join(timeout=3);server.close()
assert not worker.is_alive()