# ruff: noqa: INP001, S101 """Regression tests for board deletion cleanup behavior.""" from __future__ import annotations from dataclasses import dataclass, field from types import SimpleNamespace from typing import Any from uuid import uuid4 import pytest import app.services.board_lifecycle as board_lifecycle from app.api import boards from app.models.boards import Board from app.services.openclaw.gateway_rpc import OpenClawGatewayError _NO_EXEC_RESULTS_ERROR = "No more exec_results left for session.exec" @dataclass class _FakeSession: exec_results: list[object] executed: list[object] = field(default_factory=list) deleted: list[object] = field(default_factory=list) committed: int = 0 async def exec(self, statement: object) -> object | None: is_dml = statement.__class__.__name__ in {"Delete", "Update", "Insert"} if is_dml: self.executed.append(statement) return None if not self.exec_results: raise AssertionError(_NO_EXEC_RESULTS_ERROR) return self.exec_results.pop(0) async def execute(self, statement: object) -> None: self.executed.append(statement) async def delete(self, value: object) -> None: self.deleted.append(value) async def commit(self) -> None: self.committed += 1 @pytest.mark.asyncio async def test_delete_board_cleans_org_board_access_rows() -> None: """Deleting a board should clear org-board access rows before commit.""" session: Any = _FakeSession(exec_results=[[], []]) board = Board( id=uuid4(), organization_id=uuid4(), name="Demo Board", slug="demo-board", gateway_id=None, ) await boards.delete_board( session=session, board=board, ) deleted_table_names = [statement.table.name for statement in session.executed] assert "organization_board_access" in deleted_table_names assert "organization_invite_board_access" in deleted_table_names assert "board_task_custom_fields" in deleted_table_names assert board in session.deleted assert session.committed == 1 @pytest.mark.asyncio async def test_delete_board_cleans_tag_assignments_before_tasks() -> None: """Deleting a board should remove task-linked rows before deleting tasks.""" session: Any = _FakeSession(exec_results=[[], [uuid4()]]) board = Board( id=uuid4(), organization_id=uuid4(), name="Demo Board", slug="demo-board", gateway_id=None, ) await boards.delete_board( session=session, board=board, ) deleted_table_names = [statement.table.name for statement in session.executed] assert "tag_assignments" in deleted_table_names assert "task_custom_field_values" in deleted_table_names assert deleted_table_names.index("tag_assignments") < deleted_table_names.index("tasks") assert deleted_table_names.index("task_custom_field_values") < deleted_table_names.index( "tasks" ) @pytest.mark.asyncio async def test_delete_board_ignores_missing_gateway_agent(monkeypatch: pytest.MonkeyPatch) -> None: """Deleting a board should continue when gateway reports agent not found.""" session: Any = _FakeSession(exec_results=[[]]) board = Board( id=uuid4(), organization_id=uuid4(), name="Demo Board", slug="demo-board", gateway_id=uuid4(), ) agent = SimpleNamespace(id=uuid4(), board_id=board.id) gateway = SimpleNamespace(url="ws://gateway.example/ws", token=None, workspace_root="/tmp") called = {"delete_agent_lifecycle": 0} async def _fake_all(_session: object) -> list[object]: return [agent] async def _fake_require_gateway_for_board( _session: object, _board: object, *, require_workspace_root: bool, ) -> object: _ = require_workspace_root return gateway async def _fake_delete_agent_lifecycle( _self: object, *, agent: object, gateway: object, delete_files: bool = True, delete_session: bool = True, ) -> str | None: _ = (agent, gateway, delete_files, delete_session) called["delete_agent_lifecycle"] += 1 raise OpenClawGatewayError('agent "mc-worker" not found') monkeypatch.setattr( board_lifecycle.Agent, "objects", SimpleNamespace(filter_by=lambda **_kwargs: SimpleNamespace(all=_fake_all)), ) monkeypatch.setattr( board_lifecycle, "require_gateway_for_board", _fake_require_gateway_for_board, ) monkeypatch.setattr(board_lifecycle, "gateway_client_config", lambda _gateway: None) monkeypatch.setattr( board_lifecycle.OpenClawGatewayProvisioner, "delete_agent_lifecycle", _fake_delete_agent_lifecycle, ) await boards.delete_board( session=session, board=board, ) assert called["delete_agent_lifecycle"] == 1 assert board in session.deleted assert session.committed == 1