Files
codexpi/tests/test_activity_duck.py

95 lines
4.6 KiB
Python
Raw Normal View History

2026-11-14 20:23:59 +01:00
from __future__ import annotations
import uuid
from datetime import UTC, datetime, timedelta
def make_conversation(app, projects, owner):
root = projects / "duck"
root.mkdir()
project_id = str(uuid.uuid4())
app.state.db.execute(
"INSERT INTO projects(id,name,path,owner_id,created_at) VALUES(?,?,?,?,?)",
(project_id, "Duck project", str(root), owner, "2026-01-01"),
)
conversation = app.state.codex.create_conversation(project_id, "Long task", user_id=owner)
return project_id, conversation
def test_duck_repeated_failures_and_long_task_are_deduplicated(logged_in):
client, app, projects, _, _ = logged_in
owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"]
_, conversation = make_conversation(app, projects, owner)
for index in range(3):
app.state.db.execute(
"INSERT INTO prompt_queue(id,conversation_id,content,status,attachment_ids,created_at,finished_at,error,requested_by) VALUES(?,?,?,?,?,?,?,?,?)",
(str(uuid.uuid4()), conversation["id"], "try", "failed", "[]", f"2026-01-0{index+1}", f"2026-01-0{index+1}", f"pytest failed in test_widget line {40+index}", owner),
)
started = (datetime.now(UTC) - timedelta(minutes=45)).isoformat()
running_id = str(uuid.uuid4())
app.state.db.execute(
"INSERT INTO prompt_queue(id,conversation_id,content,status,attachment_ids,created_at,started_at,requested_by) VALUES(?,?,?,?,?,?,?,?)",
(running_id, conversation["id"], "long", "running", "[]", started, started, owner),
)
app.state.db.execute("UPDATE conversations SET status='running',updated_at=? WHERE id=?", (started, conversation["id"]))
first = client.get("/api/activity")
second = client.get("/api/activity")
assert first.status_code == second.status_code == 200
signals = app.state.db.all("SELECT * FROM duck_signals WHERE conversation_id=?", (conversation["id"],))
assert {item["kind"] for item in signals} == {"repeated_failure", "long_running"}
assert len(signals) == 2
card = next(item for item in second.json()["cards"] if item["id"] == conversation["id"])
assert card["state"] == "running"
assert len(card["signals"]) == 2
def test_duck_signal_is_private_and_can_be_dismissed(logged_in):
client, app, projects, _, headers = logged_in
owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"]
_, conversation = make_conversation(app, projects, owner)
signal_id = str(uuid.uuid4())
now = datetime.now(UTC).isoformat()
app.state.db.execute(
"INSERT INTO duck_signals(id,conversation_id,kind,fingerprint,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?)",
(signal_id, conversation["id"], "long_running", "fixture", "open", now, now),
)
response = client.post(f"/api/duck-signals/{signal_id}/action", headers=headers, json={"action": "dismiss"})
assert response.status_code == 200
assert app.state.db.one("SELECT status FROM duck_signals WHERE id=?", (signal_id,))["status"] == "dismissed"
def test_project_duration_budget_warns_without_stopping_active_agent(logged_in):
client, app, projects, _, _ = logged_in
owner = app.state.db.one("SELECT id FROM users LIMIT 1")["id"]
project_id, conversation = make_conversation(app, projects, owner)
now = datetime.now(UTC).isoformat()
app.state.db.execute(
"INSERT INTO user_preferences(user_id,auto_compact_context,duck_enabled,updated_at) VALUES(?,?,?,?) "
"ON CONFLICT(user_id) DO UPDATE SET duck_enabled=excluded.duck_enabled",
(owner, 0, 0, now),
)
app.state.db.execute(
"INSERT INTO agent_project_policies(project_id,priority,max_runtime_minutes,updated_at) VALUES(?,?,?,?)",
(project_id, "normal", 5, now),
)
started = (datetime.now(UTC) - timedelta(minutes=10)).isoformat()
queue_id = str(uuid.uuid4())
app.state.db.execute(
"INSERT INTO prompt_queue(id,conversation_id,content,status,attachment_ids,created_at,started_at,requested_by) "
"VALUES(?,?,?,?,?,?,?,?)",
(queue_id, conversation["id"], "long", "running", "[]", started, started, owner),
)
app.state.db.execute(
"UPDATE conversations SET status='running',updated_at=? WHERE id=?", (started, conversation["id"]),
)
assert client.get("/api/activity").status_code == 200
signal = app.state.db.one(
"SELECT * FROM duck_signals WHERE conversation_id=? AND kind='long_running'", (conversation["id"],),
)
assert signal and signal["queue_id"] == queue_id
assert app.state.db.one("SELECT status FROM prompt_queue WHERE id=?", (queue_id,))["status"] == "running"