Add autonomous webhooks and dynamic WebView discovery

This commit is contained in:
Ops Agent committed 2026-10-11 08:43:39 +02:00
1 parent ab69f287b6
commit 97f874d183
29 files changed
+3044 -1489

No files matched your search

+138
View File
@@ -311,3 +311,141 @@ class DesktopCompatibilityTests(unittest.TestCase):
result=desktop.check_core_build(path)
self.assertTrue(result['matches_tested_configuration'])
self.assertIsNone(result['warning'])
class WebhookTests(unittest.TestCase):
def test_listener_javascript_does_not_receive_transport_credentials(self):
from whatsapp_suite import webhook
config={'version':1,'endpoint':'https://example.test/events','token_env':'TEST_TOKEN',
'protected_token':'synthetic-protected-credential','self_only':True,'since_ms':1,
'listener_id':'91b17841-2106-4e73-97db-c38be6e9b269'}
def run(pid,suite,_operation):
report={'pid':1};_operation(None,None,report);return report
with patch.object(webhook,'run_math',side_effect=run), \
patch.object(webhook,'evaluate',return_value={'installed':True}) as evaluate, \
patch.object(webhook,'native_control',return_value={'running':True}), \
patch.object(webhook,'configuration_token',return_value='synthetic-token'):
webhook.manage('install',config)
script=evaluate.call_args.args[1]
self.assertNotIn(config['protected_token'],script)
self.assertNotIn(config['token_env'],script)
self.assertNotIn(config['endpoint'],script)
self.assertIn(config['listener_id'],script)
def test_listener_filters_decodes_deduplicates_and_stops(self):
if not shutil.which('node'):
self.skipTest('Node.js unavailable')
fixture=resource_bytes('tests/webhook_listener.cjs').decode('utf-8')
source=asset_bytes('webhook_listener.js').decode('utf-8')
# Node receives its fixture as argv code and the real producer through stdin.
result=subprocess.run(['node','-e',fixture],input=source,capture_output=True,
text=True,encoding='utf-8',timeout=10)
self.assertEqual(result.returncode,0,result.stderr)
def test_receiver_acknowledges_committed_events_and_deduplicates(self):
import threading
import urllib.request
import sqlite3
from whatsapp_suite.webhook import make_server
listener='91b17841-2106-4e73-97db-c38be6e9b269'
payload={'listener_id':listener,'events':[{'seq':1,'event_id':'synthetic-1',
'type':'message','message':{'id':'synthetic-1','body':'Grüße 🐈'}}]}
with tempfile.TemporaryDirectory() as folder:
store=Path(folder)/'events.sqlite3'
server=make_server(0,store,listener)
thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start()
try:
url='http://127.0.0.1:'+str(server.server_port)+'/whatsapp-webhook'
results=[]
for _ in range(2):
request=urllib.request.Request(url,data=json.dumps(payload).encode(),
headers={'Content-Type':'application/json'})
with urllib.request.urlopen(request,timeout=3) as response:
results.append(json.load(response)['inserted'])
self.assertEqual(results,[1,0])
with contextlib.closing(sqlite3.connect(store)) as db:
stored=json.loads(db.execute('SELECT payload FROM events').fetchone()[0])
self.assertEqual(stored['message']['body'],'Grüße 🐈')
payload['listener_id']='other'
request=urllib.request.Request(url,data=json.dumps(payload).encode())
from urllib.error import HTTPError
with self.assertRaises(HTTPError) as error:
urllib.request.urlopen(request,timeout=3)
self.assertEqual(error.exception.code,400)
finally:
server.shutdown();server.server_close();thread.join(3)
def test_receiver_storage_failure_does_not_acknowledge(self):
import threading
import urllib.request
from urllib.error import HTTPError
import sqlite3
from whatsapp_suite import webhook
with tempfile.TemporaryDirectory() as folder:
server=webhook.make_server(0,Path(folder)/'events.sqlite3')
thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start()
try:
with patch.object(webhook,'store_events',side_effect=sqlite3.OperationalError('fixture')):
request=urllib.request.Request('http://127.0.0.1:'+str(server.server_port)+'/whatsapp-webhook',data=b'{}')
with self.assertRaises(HTTPError) as error:
urllib.request.urlopen(request,timeout=3)
self.assertEqual(error.exception.code,503)
finally:
server.shutdown();server.server_close();thread.join(3)
def test_restore_loop_installs_missing_listener_without_replacing_another(self):
import threading
from whatsapp_suite import webhook
listener='91b17841-2106-4e73-97db-c38be6e9b269'
config={'version':1,'port':18763,'self_only':True,'since_ms':1,'listener_id':listener}
stopped=threading.Event();health={}
def manage(action,*args):
if action=='status':return {'passed':True,'pid':42,'listener':{'installed':False},'relay':{'running':False}}
stopped.set();return {'passed':True,'pid':42}
with patch.object(webhook,'read_config',return_value=config),patch.object(webhook,'manage',side_effect=manage) as operations:
webhook.restore_loop('fixture.json',health,stopped)
self.assertEqual([c.args[0] for c in operations.call_args_list],['status','install'])
self.assertEqual(health['restores'],1)
class OneIteration:
def __init__(self):self.done=False
def is_set(self):return self.done
def wait(self,*args):self.done=True
with patch.object(webhook,'read_config',return_value=config),patch.object(webhook,'manage',return_value={
'passed':True,'pid':42,'listener':{'installed':True,'listener_id':'other'},'relay':{'running':True}}) as operations:
webhook.restore_loop('fixture.json',health,OneIteration())
self.assertEqual(operations.call_count,1)
self.assertIn('refusing replacement',health['last_error'])
def test_receiver_configuration_and_native_asset_integrity(self):
from whatsapp_suite import webhook
config={'version':1,'port':18763,'self_only':True,'since_ms':1,
'listener_id':'91b17841-2106-4e73-97db-c38be6e9b269'}
self.assertEqual(webhook.validate_config(config),config)
with self.assertRaises(ValueError):webhook.validate_config(dict(config,port=70000))
self.assertEqual(hashlib.sha256(asset_bytes('webhook_probe.dll')).hexdigest(),webhook.HELPER_SHA256)
def test_direct_https_configuration_rejects_unsafe_routes(self):
from whatsapp_suite import webhook
config={'version':1,'self_only':True,'since_ms':1,
'listener_id':'91b17841-2106-4e73-97db-c38be6e9b269',
'endpoint':'https://receiver.example/webhook','token_env':'FIXTURE_TOKEN'}
self.assertEqual(webhook.validate_config(config),config)
for endpoint in ('http://receiver.example/webhook','https://user:[email protected]/webhook',
'https://receiver.example/webhook?token=unsafe','https://receiver.example/webhook#fragment'):
with self.assertRaises(ValueError):webhook.validate_config(dict(config,endpoint=endpoint))
@unittest.skipUnless(sys.platform=='win32','Windows DPAPI required')
def test_windows_token_protection_roundtrip_without_plaintext_in_config(self):
from whatsapp_suite import webhook
value='synthetic-webhook-token-1234567890'
protected=webhook.protect_token(value)
self.assertNotIn(value,protected)
self.assertEqual(webhook.protect_token(protected,decrypt=True),value)
def test_one_shot_restore_respects_disabled_configuration(self):
from whatsapp_suite import webhook
config={'version':1,'port':18763,'self_only':True,'since_ms':1,'enabled':False,
'listener_id':'91b17841-2106-4e73-97db-c38be6e9b269'}
with patch.object(webhook,'read_config',return_value=config),patch.object(webhook,'manage') as operation:
result=webhook.ensure_listener('fixture')
self.assertTrue(result['disabled']);operation.assert_not_called()