"""Verify current controld rejects GUI routing conflicts before invoking Cua.""" import http.client import json import os import subprocess import time assert os.environ.get('LAZYBOY_RUNNER_TEST_CONTAINER') == '1', 'disposable Computer only' child = subprocess.Popen(['/usr/local/bin/lazyboy-controld'], env={'PATH': '/usr/local/bin:/usr/bin:/bin', 'HOME': '/home/lazyboy', 'LAZYBOY_CONTROL_TOKEN': 'gui-target-fixture'}, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try: for _ in range(100): assert child.poll() is None, 'fixture daemon failed to start' try: conn = http.client.HTTPConnection('127.0.0.1', 7070, timeout=2) conn.request('GET', '/health') response = conn.getresponse() if response.status == 200: conn.close() break except OSError: time.sleep(.05) finally: conn.close() else: raise AssertionError('fixture startup deadline') cases = 0 for route, initial in [ ('act', {'actions': [], 'observe': False, 'settle_ms': 0}), ('browser', {'action': ''}), ('recording/start', {'skillId': 'fixture'}), ('recording/stop', {'skillId': 'fixture'}), ('recording/collect', {'skillId': 'fixture'}), ]: for variant in ('display', 'profile', 'duplicate-display', 'duplicate-profile', 'slot'): body = dict(initial) body['display'] = ':2' if variant == 'display' else ':1' body['profile_path' if route == 'act' else 'profilePath'] = '/profiles/b' if variant == 'profile' else '/profiles/a' encoded = json.dumps(body).encode() conn = http.client.HTTPConnection('127.0.0.1', 7070, timeout=5) conn.putrequest('POST', '/' + route) for name, value in [('Authorization', 'Bearer gui-target-fixture'), ('Content-Type', 'application/json'), ('Content-Length', str(len(encoded))), ('x-lazyboy-display', ':1'), ('x-lazyboy-profile', '/profiles/a'), ('x-lazyboy-screen-slot', '1' if variant == 'slot' else '0')]: conn.putheader(name, value) if variant.startswith('duplicate-'): conn.putheader('x-lazyboy-' + variant.removeprefix('duplicate-'), ':1' if variant.endswith('display') else '/profiles/a') conn.endheaders(encoded) response = conn.getresponse() raw = response.read() conn.close() assert response.status == 400, (route, variant, response.status, raw) assert json.loads(raw) == {'ok': False, 'error': '400 Bad Request'}, (route, variant, raw) cases += 1 for method, route in [('POST', '/observe'), ('GET', '/controller/health')]: for variant in ('empty', 'invalid', 'remote', 'slot', 'duplicate-display', 'duplicate-profile', 'duplicate-slot'): conn = http.client.HTTPConnection('127.0.0.1', 7070, timeout=5) conn.putrequest(method, route) conn.putheader('Authorization', 'Bearer gui-target-fixture') conn.putheader('Content-Length', '0') display = {'empty': '', 'invalid': ':99999', 'remote': 'remote:1'}.get(variant, ':1') conn.putheader('x-lazyboy-display', display) conn.putheader('x-lazyboy-profile', '/profiles/a') conn.putheader('x-lazyboy-screen-slot', '1' if variant == 'slot' else '0') if variant.startswith('duplicate-'): name, value = {'duplicate-display': ('x-lazyboy-display', ':1'), 'duplicate-profile': ('x-lazyboy-profile', '/profiles/a'), 'duplicate-slot': ('x-lazyboy-screen-slot', '0')}[variant] conn.putheader(name, value) conn.endheaders() response = conn.getresponse() raw = response.read() conn.close() assert response.status == 400, (route, variant, response.status, raw) assert json.loads(raw) == {'ok': False, 'error': '400 Bad Request'}, (route, variant, raw) cases += 1 print(f'controld GUI target binding: {cases} rejected conflicts/duplicates') finally: child.terminate() try: child.wait(timeout=5) except subprocess.TimeoutExpired: child.kill() child.wait()