from __future__ import annotations import json import os import socket import sys import threading import uuid from pathlib import Path from codexpi.database import Database, utcnow from codexpi.host_session_worker import main as host_worker_main def fake_host_channel(path: Path, token: str, captured: dict): ready = threading.Event() def serve(): with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as server: server.bind(str(path)) server.listen(1) ready.set() connection, _ = server.accept() with connection: request = json.loads(connection.recv(100_000).split(b"\n", 1)[0]) captured.update(request) assert request["token"] == token response = {"ok": True, "exit_code": 0, "answer": "Mesures hôte vérifiées et rapport enregistré."} connection.sendall(json.dumps(response).encode() + b"\n") thread = threading.Thread(target=serve, daemon=True) thread.start() ready.wait(2) return thread def test_host_worker_keeps_the_normal_conversation_and_queue(tmp_path: Path, monkeypatch): database = Database(tmp_path / "codexpi.db") database.initialize() project_root = tmp_path / "pi-doctor" project_root.mkdir() stamp = utcnow() database.execute( "INSERT INTO projects(id,name,path,owner_id,full_access,execution_group,created_at) VALUES(?,?,?,?,?,?,?)", ("doctor", "Pi Doctor", str(project_root), "owner", 1, "__global_agent__", stamp), ) database.execute( "INSERT INTO conversations(id,project_id,user_id,title,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?)", ("conversation", "doctor", "owner", "Audit hôte", "running", stamp, stamp), ) queue_id = str(uuid.uuid4()) message_id = str(uuid.uuid4()) database.execute( "INSERT INTO messages(id,conversation_id,role,content,metadata,created_at) VALUES(?,?,?,?,?,?)", (message_id, "conversation", "user", "Mesurer le CPU réel du Pi en lecture seule.", json.dumps({"queue_id": queue_id}), stamp), ) database.execute( "INSERT INTO prompt_queue(id,conversation_id,requested_by,message_id,content,status,created_at,started_at) VALUES(?,?,?,?,?,'running',?,?)", (queue_id, "conversation", "owner", message_id, "Mesurer le CPU réel du Pi en lecture seule.", stamp, stamp), ) token = "fixture-token-which-is-longer-than-thirty-two-characters" token_file = tmp_path / "agent.token" token_file.write_text(token, encoding="utf-8") os.chmod(token_file, 0o600) socket_path = tmp_path / "agent.sock" captured: dict = {} thread = fake_host_channel(socket_path, token, captured) events = tmp_path / "events.jsonl" monkeypatch.setattr(sys, "argv", [ "host-worker", "--database", str(database.path), "--events", str(events), "--conversation", "conversation", "--cwd", str(project_root), "--socket", str(socket_path), "--token-file", str(token_file), "--queue", queue_id, "--privileged", ]) assert host_worker_main() == 0 thread.join(2) assert captured["privileged"] is True assert "véritable hôte Raspberry Pi" in captured["prompt"] assert str(project_root) in captured["prompt"] assert database.one("SELECT status FROM prompt_queue WHERE id=?", (queue_id,))["status"] == "done" conversation = database.one("SELECT status,worker_pid FROM conversations WHERE id='conversation'") assert conversation == {"status": "completed", "worker_pid": None} answer = database.one("SELECT content,metadata FROM messages WHERE conversation_id='conversation' AND role='assistant'") assert answer["content"] == "Mesures hôte vérifiées et rapport enregistré." assert json.loads(answer["metadata"])["host_agent"] is True